Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> prevail, in peace and freedom from fear, and in true * health, through the purity and essence of our natural... fluids. * - General Turgidson */ class PureFunctionIdentifier implements CompilerPass { static final DiagnosticType INVALID_NO_SIDE_EFFECT_ANNOTATION = DiagnosticType.error( "JSC_INVALID_NO_SIDE_EFFECT_ANNOTATION", "@nosideeffects may only appear in externs files."); private final AbstractCompiler compiler; private final DefinitionProvider definitionProvider; // Function node -> function side effects map private final Map<Node, FunctionInformation> functionSideEffectMap; // List of all function call sites; used to iterate in markPureFunctionCalls. private final List<Node> allFunctionCalls; // Externs and ast tree root, for use in getDebugReport. These two // fields are null until process is called. private Node externs; private Node root; public PureFunctionIdentifier(AbstractCompiler compiler, DefinitionProvider definitionProvider) { this.compiler = compiler; this.definitionProvider = definitionProvider; this.functionSideEffectMap = Maps.newHashMap(); this.allFunctionCalls = Lists.newArrayList(); this.externs = null; this.root = null; } @Override public void process(Node externsAst, Node srcAst) { if (externs != null || root != null) { throw new IllegalStateException( "It is illegal to call PureFunctionIdentifier.process " + "twice the same instance. Please use a new " + "PureFunctionIdentifier instance each time."); } externs = externsAst; root = srcAst; NodeTraversal.traverse(compiler, externs, new FunctionAnalyzer(true)); NodeTraversal.traverse(compiler, root, new FunctionAnalyzer(false)); propagateSideEffects(); markPureFunctionCalls(); } /** * Compute debug report that includes: * - List of all pure functions. * - Reasons we think the remaining functions have side effects. */ String getDebugReport() { Preconditions.checkNotNull(externs); Preconditions.checkNotNull(root); StringBuilder sb = new StringBuilder(); FunctionNames functionNames = new FunctionNames(compiler); functionNames.process(null,

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> for (Definition def : defs) { Node defValue = def.getRValue(); FunctionInformation dep = functionSideEffectMap.get(defValue); Preconditions.checkNotNull(dep); sideEffectGraph.connect(dep, callSite, functionInfo); } } } // Propagate side effect information to a fixed point. FixedPointGraphTraversal.newTraversal(new SideEffectPropagationCallback()) .computeFixedPoint(sideEffectGraph); // Mark remaining functions "pure". for (FunctionInformation functionInfo : functionSideEffectMap.values()) { if (functionInfo.mayBePure()) { functionInfo.setIsPure(); } } } /** * Set no side effect property at pure-function call sites. */ private void markPureFunctionCalls() { for (Node callNode : allFunctionCalls) { Node name = callNode.getFirstChild(); Collection<Definition> defs = getCallableDefinitions(definitionProvider, name); if (defs == null) { continue; } boolean hasSideEffects = false; for (Definition def : defs) { FunctionInformation functionInfo = functionSideEffectMap.get(def.getRValue()); Preconditions.checkNotNull(functionInfo); if ((NodeUtil.isCall(callNode) && functionInfo.mayHaveSideEffects()) || (NodeUtil.isNew(callNode) && (functionInfo.mutatesGlobalState() || functionInfo.functionThrows()))) { hasSideEffects = true; break; } } if (!hasSideEffects) { callNode.setIsNoSideEffectsCall(); } } } /** * Gather list of functions, functions with @nosideeffect * annotations, call sites, and functions that may mutate variables * not defined in the local scope. */ private class FunctionAnalyzer implements Callback { private final boolean inExterns; FunctionAnalyzer(boolean inExterns) { this.inExterns = inExterns; } @Override public boolean shouldTraverse(NodeTraversal traversal, Node node, Node parent) { // Functions need to be processed as part of pre-traversal so an // entry for the enclosing function exists in the // FunctionInformation map when processing assignments and calls // inside visit. if (

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>NodeUtil.isFunction(node)) { Node gramp = parent.getParent(); visitFunction(traversal, node, parent, gramp); } return true; } @Override public void visit(NodeTraversal traversal, Node node, Node parent) { if (inExterns) { return; } if (!NodeUtil.nodeTypeMayHaveSideEffects(node)) { return; } if (NodeUtil.isCall(node) || NodeUtil.isNew(node)) { allFunctionCalls.add(node); } Node enclosingFunction = traversal.getEnclosingFunction(); if (enclosingFunction != null) { FunctionInformation sideEffectInfo = functionSideEffectMap.get(enclosingFunction); Preconditions.checkNotNull(sideEffectInfo); if (NodeUtil.isAssignmentOp(node)) { visitAssignmentOrUnaryOperatorLhs( sideEffectInfo, traversal.getScope(), node.getFirstChild()); } else { switch(node.getType()) { case Token.CALL: case Token.NEW: visitCall(sideEffectInfo, node); break; case Token.DELPROP: case Token.DEC: case Token.INC: visitAssignmentOrUnaryOperatorLhs( sideEffectInfo, traversal.getScope(), node.getFirstChild()); break; case Token.NAME: // Variable definition are not side effects. // Just check that the name appears in the context of a // variable declaration. Preconditions.checkArgument( NodeUtil.isVarDeclaration(node)); break; case Token.THROW: visitThrow(sideEffectInfo); break; default: throw new IllegalArgumentException( "Unhandled side effect node type " + Token.name(node.getType())); } } } } /** * Record information about the side effects caused by an * assigment or mutating unary operator. * * If the operation modifies this or taints global state, mark the * enclosing function as having those side effects. */ private void visitAssignmentOrUnaryOperatorLhs( FunctionInformation sideEffectInfo, Scope scope, Node lhs) { if (NodeUtil.isName(lhs)) { Var var = scope.getVar(lhs.getString()); if (var == null ||

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> var.scope != scope) { sideEffectInfo.setTaintsGlobalState(); } } else if (NodeUtil.isGetProp(lhs)) { if (NodeUtil.isThis(lhs.getFirstChild())) { sideEffectInfo.setTaintsThis(); } else { sideEffectInfo.setTaintsUnknown(); } } else { sideEffectInfo.setTaintsUnknown(); } } /** * Record information about a call site. */ private void visitCall(FunctionInformation sideEffectInfo, Node node) { sideEffectInfo.appendCall(node); } /** * Record function and check for @nosideeffects annotations. */ private void visitFunction(NodeTraversal traversal, Node node, Node parent, Node gramp) { Preconditions.checkArgument(!functionSideEffectMap.containsKey(node)); FunctionInformation sideEffectInfo = new FunctionInformation(inExterns); functionSideEffectMap.put(node, sideEffectInfo); if (hasNoSideEffectsAnnotation(node, parent, gramp)) { if (inExterns) { sideEffectInfo.setIsPure(); } else { traversal.report(node, INVALID_NO_SIDE_EFFECT_ANNOTATION); } } else if (inExterns) { sideEffectInfo.setTaintsGlobalState(); } } /** * Record that the enclosing function throws. */ private void visitThrow(FunctionInformation sideEffectInfo) { sideEffectInfo.setFunctionThrows(); } /** * Get the value of the @nosideeffects annotation stored in the * doc info. */ private boolean hasNoSideEffectsAnnotation(Node node, Node parent, Node gramp) { { JSDocInfo docInfo = node.getJSDocInfo(); if (docInfo != null && docInfo.isNoSideEffects()) { return true; } } if (NodeUtil.isName(parent)) { JSDocInfo docInfo = gramp.getJSDocInfo(); return gramp.hasOneChild() && docInfo != null && docInfo.isNoSideEffects(); } else if (NodeUtil.isAssign(parent)) { JSDocInfo docInfo = parent

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>.getJSDocInfo(); return docInfo != null && docInfo.isNoSideEffects(); } else { return false; } } } /** * Callback that propagates side effect information across call sites. */ private static class SideEffectPropagationCallback implements EdgeCallback<FunctionInformation, Node> { public boolean traverseEdge(FunctionInformation callee, Node callSite, FunctionInformation caller) { Preconditions.checkArgument(callSite.getType() == Token.CALL || callSite.getType() == Token.NEW); boolean changed = false; if (!caller.mutatesGlobalState() && callee.mutatesGlobalState()) { caller.setTaintsGlobalState(); changed = true; } if (!caller.functionThrows() && callee.functionThrows()) { caller.setFunctionThrows(); changed = true; } if (callee.mutatesThis()) { // Side effects only propagate via regular calls. // Calling a constructor that modifies "this" has no side effects. if (callSite.getType() != Token.NEW) { Node objectNode = getCallThisObject(callSite); if (objectNode != null && NodeUtil.isThis(objectNode)) { if (!caller.mutatesThis()) { caller.setTaintsThis(); changed = true; } } else if (!caller.mutatesGlobalState()) { caller.setTaintsGlobalState(); changed = true; } } } return changed; } } /** * Analyze a call site and extract the node that will be act as * "this" inside the call, which is either the object part of the * qualified function name, the first argument to the call in the * case of ".call" and ".apply" or null if object is not specified * in either of those ways. * * @return node that will act as "this" for the call. */ private static Node getCallThisObject(Node callSite) { Node foo = callSite.getFirstChild(); if (!NodeUtil.isGetProp(foo)) { // "this" is not specified explicitly; call modifies global "this". return null; } Node object = null; String propString = foo.getLast

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> id which ops may have side effects, id the ones // that we know to be safe switch (n.getType()) { // other side-effect free statements and expressions case Token.AND: case Token.BLOCK: case Token.EXPR_RESULT: case Token.HOOK: case Token.IF: case Token.IN: case Token.LP: case Token.NUMBER: case Token.OR: case Token.THIS: case Token.TRUE: case Token.FALSE: case Token.NULL: case Token.STRING: case Token.SWITCH: case Token.TRY: case Token.EMPTY: break; // Throws are by definition side effects case Token.THROW: return true; case Token.OBJECTLIT: case Token.ARRAYLIT: case Token.REGEXP: if (checkForNewObjects) { return true; } break; case Token.VAR: // empty var statement (no declaration) case Token.NAME: // variable by itself if (n.getFirstChild() != null) return true; break; case Token.FUNCTION: // Anonymous functions don't have side-effects, but named ones // change the namespace. Therefore, we check if the function has // a name. Either way, we don't need to check the children, since // they aren't executed at declaration time. // return !isFunctionAnonymous(n); case Token.NEW: { if (checkForNewObjects) { return true; } // calls to constructors that have no side effects have the // no side effect property set. if (n.isNoSideEffectsCall()) { break; } // certain constructors are certified side effect free Node constructor = n.getFirstChild(); if (Token.NAME == constructor.getType()) { String className = constructor.getString(); if (CONSTRUCTORS_WITHOUT_SIDE_EFFECTS.contains(className)) { // loop below will see if the constructor parameters have // side-effects break; } } else { // the constructor could also be an expression like // new (useArray ? Object : Array)(); } } return true; case Token.CALL: // calls to functions that have no side effects have the no

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> // side effect property set. if (n.isNoSideEffectsCall()) { // loop below will see if the function parameters have // side-effects break; } return true; default: if (isSimpleOperatorType(n.getType())) break; if (isAssignmentOp(n)) { // Assignments will have side effects if // a) The RHS has side effects, or // b) The LHS has side effects, or // c) A name on the LHS will exist beyond the life of this statement. if (checkForStateChangeHelper( n.getFirstChild(), checkForNewObjects) || checkForStateChangeHelper( n.getLastChild(), checkForNewObjects)) { return true; } Node current = n.getFirstChild(); for (; current.getType() == Token.GETPROP || current.getType() == Token.GETELEM; current = current.getFirstChild()) { } return !(isLiteralValue(current) || current.getType() == Token.FUNCTION); } return true; } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (checkForStateChangeHelper(c, checkForNewObjects)) { return true; } } return false; } /** * Do calls to this constructor have side effects? * * @param callNode - construtor call node */ static boolean constructorCallHasSideEffects(Node callNode) { Preconditions.checkArgument( callNode.getType() == Token.NEW, "Expected NEW node, got " + Token.name(callNode.getType())); if (callNode.isNoSideEffectsCall()) { return false; } Node nameNode = callNode.getFirstChild(); if (nameNode.getType() == Token.NAME && CONSTRUCTORS_WITHOUT_SIDE_EFFECTS.contains(nameNode.getString())) { return false; } return true; } /** * Returns true if calls to this function have side effects. * * @param callNode - function call node */ static boolean functionCallHasSideEffects(Node callNode) { Preconditions.checkArgument( callNode.getType() == Token.CALL, "Expected CALL node,

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>wise-and & * 8 equality == != * 9 relational < <= > >= * 10 bitwise shift << >> >>> * 11 addition/subtraction + - * 12 multiply/divide * / % * 13 negation/increment ! ~ - ++ -- * 14 call, member () [] . */ static int precedence(int type) { switch (type) { case Token.COMMA: return 0; case Token.ASSIGN_BITOR: case Token.ASSIGN_BITXOR: case Token.ASSIGN_BITAND: case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.ASSIGN_URSH: case Token.ASSIGN_ADD: case Token.ASSIGN_SUB: case Token.ASSIGN_MUL: case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: case Token.ASSIGN: return 1; case Token.HOOK: return 2; // ?: operator case Token.OR: return 3; case Token.AND: return 4; case Token.BITOR: return 5; case Token.BITXOR: return 6; case Token.BITAND: return 7; case Token.EQ: case Token.NE: case Token.SHEQ: case Token.SHNE: return 8; case Token.LT: case Token.GT: case Token.LE: case Token.GE: case Token.INSTANCEOF: case Token.IN: return 9; case Token.LSH: case Token.RSH: case Token.URSH: return 10; case Token.SUB: case Token.ADD: return 11; case Token.MUL: case Token.MOD: case Token.DIV: return 12; case Token.INC: case Token.DEC: case Token.NEW: case Token.DELPROP: case Token.TYPEOF: case Token.VOID: case Token.NOT: case Token.BITNOT: case Token.POS: case Token.NEG: return 13; case Token.ARRAYLIT: case Token.CALL: case Token

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>.EMPTY: case Token.FALSE: case Token.FUNCTION: case Token.GETELEM: case Token.GETPROP: case Token.GET_REF: case Token.IF: case Token.LP: case Token.NAME: case Token.NULL: case Token.NUMBER: case Token.OBJECTLIT: case Token.REGEXP: case Token.RETURN: case Token.STRING: case Token.THIS: case Token.TRUE: return 15; default: throw new Error("Unknown precedence for " + Node.tokenToName(type) + " (type " + type + ")"); } } /** * Returns true if the operator is associative. * e.g. (a * b) * c = a * (b * c) * Note: "+" is not associative because it is also the concatentation * for strings. e.g. "a" + (1 + 2) is not "a" + 1 + 2 */ static boolean isAssociative(int type) { switch (type) { case Token.MUL: case Token.AND: case Token.OR: case Token.BITOR: case Token.BITAND: return true; default: return false; } } static boolean isAssignmentOp(Node n) { switch (n.getType()){ case Token.ASSIGN: case Token.ASSIGN_BITOR: case Token.ASSIGN_BITXOR: case Token.ASSIGN_BITAND: case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.ASSIGN_URSH: case Token.ASSIGN_ADD: case Token.ASSIGN_SUB: case Token.ASSIGN_MUL: case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: return true; } return false; } static int getOpFromAssignmentOp(Node n) { switch (n.getType()){ case Token.ASSIGN_BITOR: return Token.BITOR; case Token.ASSIGN_BITXOR: return Token.BITXOR; case Token.ASSIGN_BITAND: return Token.BITAND; case Token.ASSIGN_LSH: return Token.LSH;

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> case Token.ASSIGN_RSH: return Token.RSH; case Token.ASSIGN_URSH: return Token.URSH; case Token.ASSIGN_ADD: return Token.ADD; case Token.ASSIGN_SUB: return Token.SUB; case Token.ASSIGN_MUL: return Token.MUL; case Token.ASSIGN_DIV: return Token.DIV; case Token.ASSIGN_MOD: return Token.MOD; } throw new IllegalArgumentException("Not an assiment op"); } static boolean isExpressionNode(Node n) { return n.getType() == Token.EXPR_RESULT; } /** * Determines if the given node contains a function declaration. */ static boolean containsFunctionDeclaration(Node n) { return containsType(n, Token.FUNCTION); } /** * Returns true if the subtree contains references to 'this' keyword */ static boolean referencesThis(Node n) { return containsType(n, Token.THIS); } /** * Is this a GETPROP or GETELEM node? */ static boolean isGet(Node n) { return n.getType() == Token.GETPROP || n.getType() == Token.GETELEM; } /** * Is this a GETPROP node? */ static boolean isGetProp(Node n) { return n.getType() == Token.GETPROP; } /** * Is this a NAME node? */ static boolean isName(Node n) { return n.getType() == Token.NAME; } /** * Is this a NEW node? */ static boolean isNew(Node n) { return n.getType() == Token.NEW; } /** * Is this a VAR node? */ static boolean isVar(Node n) { return n.getType() == Token.VAR; } /** * Is this node the name of a variable being declared? * * @param n The node * @return True if {@code n} is NAME and {@code parent} is VAR */ static boolean isVarDeclaration(Node n) { // There is no need to verify that parent != null because a NAME node // always has a parent in a valid parse tree. return n.getType() == Token

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>.NAME && n.getParent().getType() == Token.VAR; } /** * For an assignment or variable declaration get the assigned value. * @return The value node representing the new value. */ static Node getAssignedValue(Node n) { Preconditions.checkState(isName(n)); Node parent = n.getParent(); if (isVar(parent)) { return n.getFirstChild(); } else if (isAssign(parent) && parent.getFirstChild() == n) { return n.getNext(); } else { return null; } } /** * Is this a STRING node? */ static boolean isString(Node n) { return n.getType() == Token.STRING; } /** * Is this node an assignment expression statement? * * @param n The node * @return True if {@code n} is EXPR_RESULT and {@code n}'s * first child is ASSIGN */ static boolean isExprAssign(Node n) { return n.getType() == Token.EXPR_RESULT && n.getFirstChild().getType() == Token.ASSIGN; } /** * Is this an ASSIGN node? */ static boolean isAssign(Node n) { return n.getType() == Token.ASSIGN; } /** * Is this node a call expression statement? * * @param n The node * @return True if {@code n} is EXPR_RESULT and {@code n}'s * first child is CALL */ static boolean isExprCall(Node n) { return n.getType() == Token.EXPR_RESULT && n.getFirstChild().getType() == Token.CALL; } /** * @return Whether the node represents a FOR-IN loop. */ static boolean isForIn(Node n) { return n.getType() == Token.FOR && n.getChildCount() == 3; } /** * Determines whether the given node is a FOR, DO, or WHILE node. */ static boolean isLoopStructure(Node n) { switch (n.getType()) { case Token.FOR: case Token.DO: case Token.WHILE: return true; default: return false; } }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> /** * @param n The node to inspect. * @return If the node, is a FOR, WHILE, or DO, it returns the node for * the code BLOCK, null otherwise. */ static Node getLoopCodeBlock(Node n) { switch (n.getType()) { case Token.FOR: case Token.WHILE: return n.getLastChild(); case Token.DO: return n.getFirstChild(); default: return null; } } /** * Determines whether the given node is a FOR, DO, WHILE, WITH, or IF node. */ static boolean isControlStructure(Node n) { switch (n.getType()) { case Token.FOR: case Token.DO: case Token.WHILE: case Token.WITH: case Token.IF: case Token.LABEL: case Token.TRY: case Token.CATCH: case Token.SWITCH: case Token.CASE: case Token.DEFAULT: return true; default: return false; } } /** * Determines whether the given node is code node for FOR, DO, * WHILE, WITH, or IF node. */ static boolean isControlStructureCodeBlock(Node parent, Node n) { switch (parent.getType()) { case Token.FOR: case Token.WHILE: case Token.LABEL: case Token.WITH: return parent.getLastChild() == n; case Token.DO: return parent.getFirstChild() == n; case Token.IF: return parent.getFirstChild() != n; case Token.TRY: return parent.getFirstChild() == n || parent.getLastChild() == n; case Token.CATCH: return parent.getLastChild() == n; case Token.SWITCH: case Token.CASE: return parent.getFirstChild() != n; case Token.DEFAULT: return true; default: Preconditions.checkState(isControlStructure(parent)); return false; } } /** * Gets the condition of an ON_TRUE / ON_FALSE CFG edge. * @param n a node with an outgoing conditional CFG edge * @return the condition node or null if the condition is not obviously a node */ static Node getConditionExpression

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>(Node n) { switch (n.getType()) { case Token.IF: case Token.WHILE: return n.getFirstChild(); case Token.DO: return n.getLastChild(); case Token.FOR: switch (n.getChildCount()) { case 3: return null; case 4: return n.getFirstChild().getNext(); } throw new IllegalArgumentException("malformed 'for' statement " + n); case Token.CASE: return null; } throw new IllegalArgumentException(n + " does not have a condition."); } /** * @return Whether the node is of a type that contain other statements. */ static boolean isStatementBlock(Node n) { return n.getType() == Token.SCRIPT || n.getType() == Token.BLOCK; } /** * @return Whether the node is used as a statement. */ static boolean isStatement(Node n) { Node parent = n.getParent(); // It is not possible to determine definitely if a node is a statement // or not if it is not part of the AST. A FUNCTION node, for instance, // is either part of an expression (as a anonymous function) or as // a statement. Preconditions.checkState(parent != null); switch (parent.getType()) { case Token.SCRIPT: case Token.BLOCK: case Token.LABEL: return true; default: return false; } } /** Whether the node is part of a switch statement. */ static boolean isSwitchCase(Node n) { return n.getType() == Token.CASE || n.getType() == Token.DEFAULT; } /** * @return Whether the name is a reference to a variable, function or * function parameter (not a label or a empty anonymous function name). */ static boolean isReferenceName(Node n) { return isName(n) && !n.getString().isEmpty() && !isLabelName(n); } /** @return Whether the node is a label name. */ static boolean isLabelName(Node n) { if (n != null && n.getType() == Token.NAME) { Node parent = n.getParent(); switch (parent.getType()) { case Token.LABEL: case Token

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> removed. */ static boolean tryMergeBlock(Node block) { Preconditions.checkState(block.getType() == Token.BLOCK); Node parent = block.getParent(); // Try to remove the block if its parent is a block/script or if its // parent is label and it has exactly one child. if (NodeUtil.isStatementBlock(parent)) { Node previous = block; while (block.hasChildren()) { Node child = block.removeFirstChild(); parent.addChildAfter(child, previous); previous = child; } parent.removeChild(block); return true; } else if (parent.getType() == Token.LABEL && block.hasOneChild()) { parent.replaceChild(block, block.removeFirstChild()); return true; } else { return false; } } /** * Is this a CALL node? */ static boolean isCall(Node n) { return n.getType() == Token.CALL; } /** * Is this a FUNCTION node? */ static boolean isFunction(Node n) { return n.getType() == Token.FUNCTION; } /** * Return a BLOCK node for the given FUNCTION node. */ static Node getFunctionBody(Node fn) { Preconditions.checkArgument(isFunction(fn)); return fn.getLastChild(); } /** * Is this a THIS node? */ static boolean isThis(Node node) { return node.getType() == Token.THIS; } /** * Is this node or any of its children a CALL? */ static boolean containsCall(Node n) { return containsType(n, Token.CALL); } /** * Is this node a function declaration? A function declaration is a function * that has a name that is added to the current scope (i.e. a function that * is not anonymous; see {@link #isFunctionAnonymous}). */ static boolean isFunctionDeclaration(Node n) { return n.getType() == Token.FUNCTION && !isFunctionAnonymous(n); } /** * Is this node a hoisted function declaration? A function declaration in the * scope root is hoisted to the top of the scope. * See {@link #isFunctionDeclaration}). */ static boolean isHoistedFunctionDeclaration

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>(Node n) { return NodeUtil.isFunctionDeclaration(n) && (n.getParent().getType() == Token.SCRIPT || n.getParent().getParent().getType() == Token.FUNCTION); } /** * Is this node an anonymous function? An anonymous function is one that has * either no name or a name that is not added to the current scope (see * {@link #isFunctionAnonymous}). */ static boolean isAnonymousFunction(Node n) { return n.getType() == Token.FUNCTION && isFunctionAnonymous(n); } /** * Is a FUNCTION node an anonymous function? An anonymous function is one that * has either no name or a name that is not added to the current scope. * * <p>Some examples of anonymous functions: * <pre> * function () {} * (function f() {})() * [ function f() {} ] * var f = function f() {}; * for (function f() {};;) {} * </pre> * * <p>Some examples of functions that are <em>not</em> anonymous: * <pre> * function f() {} * if (x); else function f() {} * for (;;) { function f() {} } * </pre> * * @param n A FUNCTION node * @return Whether n is an anonymous function */ static boolean isFunctionAnonymous(Node n) { return !isStatement(n); } /** * Determines if a function takes a variable number of arguments by * looking for references to the "arguments" var_args object. */ static boolean isVarArgsFunction(Node function) { Preconditions.checkArgument(isFunction(function)); return NodeUtil.isNameReferenced( function.getLastChild(), "arguments", Predicates.<Node>not(new NodeUtil.MatchNodeType(Token.FUNCTION))); } /** * @return Whether node is a call to methodName. * a.f(...) * a['f'](...) */ static boolean isObjectCallMethod(Node callNode, String methodName) { if (callNode.getType() == Token.CALL) { Node functionIndentifyingExpression = callNode.getFirstChild(); if (NodeUtil.isGet(functionIndentifyingExpression)) { Node last =

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>LIT) { int index = 0; for (Node current = parent.getFirstChild(); current != null; current = current.getNext()) { if (current == node) { return index % 2 == 0; } index++; } } return false; } /** * Converts an operator's token value (see {@link Token}) to a string * representation. * * @param operator the operator's token value to convert * @return the string representation or {@code null} if the token value is * not an operator */ static String opToStr(int operator) { switch (operator) { case Token.BITOR: return "|"; case Token.OR: return "||"; case Token.BITXOR: return "^"; case Token.AND: return "&&"; case Token.BITAND: return "&"; case Token.SHEQ: return "==="; case Token.EQ: return "=="; case Token.NOT: return "!"; case Token.NE: return "!="; case Token.SHNE: return "!=="; case Token.LSH: return "<<"; case Token.IN: return "in"; case Token.LE: return "<="; case Token.LT: return "<"; case Token.URSH: return ">>>"; case Token.RSH: return ">>"; case Token.GE: return ">="; case Token.GT: return ">"; case Token.MUL: return "*"; case Token.DIV: return "/"; case Token.MOD: return "%"; case Token.BITNOT: return "~"; case Token.ADD: return "+"; case Token.SUB: return "-"; case Token.POS: return "+"; case Token.NEG: return "-"; case Token.ASSIGN: return "="; case Token.ASSIGN_BITOR: return "|="; case Token.ASSIGN_BITXOR: return "^="; case Token.ASSIGN_BITAND: return "&="; case Token.ASSIGN_LSH: return "<<="; case Token.ASSIGN_RSH: return ">>="; case Token.ASSIGN_URSH: return ">>>="; case Token.ASSIGN_ADD: return "+="; case Token.ASSIGN_SUB

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>: return "-="; case Token.ASSIGN_MUL: return "*="; case Token.ASSIGN_DIV: return "/="; case Token.ASSIGN_MOD: return "%="; case Token.VOID: return "void"; case Token.TYPEOF: return "typeof"; case Token.INSTANCEOF: return "instanceof"; default: return null; } } /** * Converts an operator's token value (see {@link Token}) to a string * representation or fails. * * @param operator the operator's token value to convert * @return the string representation * @throws Error if the token value is not an operator */ static String opToStrNoFail(int operator) { String res = opToStr(operator); if (res == null) { throw new Error("Unknown op " + operator + ": " + Token.name(operator)); } return res; } /** * @return true if n or any of its children are of the specified type. * Does not traverse into functions. */ static boolean containsTypeInOuterScope(Node node, int type) { return containsType(node, type, Predicates.<Node>not(new NodeUtil.MatchNodeType(Token.FUNCTION))); } /** * @return true if n or any of its children are of the specified type */ static boolean containsType(Node node, int type, Predicate<Node> traverseChildrenPred) { return has(node, new MatchNodeType(type), traverseChildrenPred); } /** * @return true if n or any of its children are of the specified type */ static boolean containsType(Node node, int type) { return containsType(node, type, Predicates.<Node>alwaysTrue()); } /** * Given a node tree, finds all the VAR declarations in that tree that are * not in an inner scope. Then adds a new VAR node at the top of the current * scope that redeclares them, if necessary. */ static void redeclareVarsInsideBranch(Node branch) { Collection<Node> vars = getVarsDeclaredInBranch(branch); if (vars.isEmpty()) { return; } Node parent = getAddingRoot(branch); for (Node nameNode : vars

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>) { Node var = new Node( Token.VAR, Node.newString(Token.NAME, nameNode.getString())); copyNameAnnotations(nameNode, var.getFirstChild()); parent.addChildToFront(var); } } /** * Copy any annotations that follow a named value. * @param source * @param destination */ static void copyNameAnnotations(Node source, Node destination) { if (source.getBooleanProp(Node.IS_CONSTANT_NAME)) { destination.putBooleanProp(Node.IS_CONSTANT_NAME, true); } } /** * Gets a Node at the top of the current scope where we can add new var * declarations as children. */ private static Node getAddingRoot(Node n) { Node addingRoot = null; Node ancestor = n; while (null != (ancestor = ancestor.getParent())) { int type = ancestor.getType(); if (type == Token.SCRIPT) { addingRoot = ancestor; break; } else if (type == Token.FUNCTION) { addingRoot = ancestor.getLastChild(); break; } } // make sure that the adding root looks ok Preconditions.checkState(addingRoot.getType() == Token.BLOCK || addingRoot.getType() == Token.SCRIPT); Preconditions.checkState(addingRoot.getFirstChild() == null || addingRoot.getFirstChild().getType() != Token.SCRIPT); return addingRoot; } /** Creates function name(params_0, ..., params_n) { body }. */ public static FunctionNode newFunctionNode(String name, List<Node> params, Node body, int lineno, int charno) { Node parameterParen = new Node(Token.LP, lineno, charno); for (Node param : params) { parameterParen.addChildToBack(param); } FunctionNode function = new FunctionNode(name, lineno, charno); function.addChildrenToBack( Node.newString(Token.NAME, name, lineno, charno)); function.addChildToBack(parameterParen); function.addChildToBack(body); return function; } /** * Creates a node representing a qualified name. * * @param name A qualified name (e.g. "foo" or

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>7f; int len = s.length(); for (int index = 0; index < len; index++) { char c = s.charAt(index); if (c > LARGEST_BASIC_LATIN) { return false; } } return true; } /** * Determines whether the given name can appear on the right side of * the dot operator. Many properties (like reserved words) cannot. */ static boolean isValidPropertyName(String name) { return TokenStream.isJSIdentifier(name) && !TokenStream.isKeyword(name) && // no Unicode escaped characters - some browsers are less tolerant // of Unicode characters that might be valid according to the // language spec. // Note that by this point, unicode escapes have been converted // to UTF-16 characters, so we're only searching for character // values, not escapes. NodeUtil.isLatin(name); } private static class VarCollector implements Visitor { final Map<String, Node> vars = Maps.newLinkedHashMap(); public void visit(Node n) { if (n.getType() == Token.NAME) { Node parent = n.getParent(); if (parent != null && parent.getType() == Token.VAR) { String name = n.getString(); if (!vars.containsKey(name)) { vars.put(name, n); } } } } } /** * Retrieves vars declared in the current node tree, excluding descent scopes. */ public static Collection<Node> getVarsDeclaredInBranch(Node root) { VarCollector collector = new VarCollector(); visitPreOrder( root, collector, Predicates.<Node>not(new NodeUtil.MatchNodeType(Token.FUNCTION))); return collector.vars.values(); } /** * @return {@code true} if the node an assignment to a prototype property of * some constructor. */ static boolean isPrototypePropertyDeclaration(Node n) { if (!NodeUtil.isExprAssign(n)) { return false; } return isPrototypeProperty(n.getFirstChild().getFirstChild()); } static boolean isPrototypeProperty(Node n) { String lhsString = n.getQualifiedName(); if (lhsString ==

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> if (pred.apply(n)) { total++; } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { total += getCount(c, pred); } return total; } /** * Interface for use with the visit method. * @see #visit */ static interface Visitor { void visit(Node node); } /** * A pre-order traversal, calling Vistor.visit for each child matching * the predicate. */ static void visitPreOrder(Node node, Visitor vistor, Predicate<Node> traverseChildrenPred) { vistor.visit(node); if (traverseChildrenPred.apply(node)) { for (Node c = node.getFirstChild(); c != null; c = c.getNext()) { visitPreOrder(c, vistor, traverseChildrenPred); } } } /** * A post-order traversal, calling Vistor.visit for each child matching * the predicate. */ static void visitPostOrder(Node node, Visitor vistor, Predicate<Node> traverseChildrenPred) { if (traverseChildrenPred.apply(node)) { for (Node c = node.getFirstChild(); c != null; c = c.getNext()) { visitPostOrder(c, vistor, traverseChildrenPred); } } vistor.visit(node); } /** * @return Whether a TRY node has a finally block. */ static boolean hasFinally(Node n) { Preconditions.checkArgument(n.getType() == Token.TRY); return n.getChildCount() == 3; } /** * @return The BLOCK node containing the CATCH node (if any) * of a TRY. */ static Node getCatchBlock(Node n) { Preconditions.checkArgument(n.getType() == Token.TRY); return n.getFirstChild().getNext(); } /** * @return Whether BLOCK (from a TRY node) contains a CATCH. * @see NodeUtil#getCatchBlock */ static boolean hasCatchHandler(Node n) { Preconditions.checkArgument(n.getType() == Token.BLOCK); return n.hasChildren() && n.getFirstChild().getType() == Token.CATCH

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>; } /** * @param fnNode The function. * @return The Node containing the Function parameters. */ static Node getFnParameters(Node fnNode) { // Function NODE: [ FUNCTION -> NAME, LP -> ARG1, ARG2, ... ] Preconditions.checkArgument(fnNode.getType() == Token.FUNCTION); return fnNode.getFirstChild().getNext(); } /** * Returns true if a name node represents a constant variable. * * <p>Determining whether a variable is constant has three steps: * <ol> * <li>In CodingConventionAnnotator, any name that matches the * {@link CodingConvention#isConstant(String)} is annotated with an * IS_CONSTANT_NAME property. * <li>The normalize pass renames any variable with the IS_CONSTANT_NAME * annotation and that is initialized to a constant value with * a variable name inlucding $$constant. * <li>Return true here if the variable includes $$constant in its name. * </ol> * * @param node A NAME or STRING node * @return True if the variable is constant */ static boolean isConstantName(Node node) { return node.getBooleanProp(Node.IS_CONSTANT_NAME); } /** * @param nameNode A name node * @return The JSDocInfo for the name node */ static JSDocInfo getInfoForNameNode(Node nameNode) { JSDocInfo info = null; Node parent = null; if (nameNode != null) { info = nameNode.getJSDocInfo(); parent = nameNode.getParent(); } if (info == null && parent != null && ((parent.getType() == Token.VAR && parent.hasOneChild()) || parent.getType() == Token.FUNCTION)) { info = parent.getJSDocInfo(); } return info; } /** * @param n The node. * @return The source name property on the node or its ancestors. */ static String getSourceName(Node n) { String sourceName = null; while (sourceName == null && n != null) { sourceName = (String) n.getProp(Node.SOUR

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> Id_export = Token.EXPORT, Id_false = Token.FALSE, Id_for = Token.FOR, Id_function = Token.FUNCTION, Id_if = Token.IF, Id_in = Token.IN, Id_new = Token.NEW, Id_null = Token.NULL, Id_return = Token.RETURN, Id_switch = Token.SWITCH, Id_this = Token.THIS, Id_true = Token.TRUE, Id_typeof = Token.TYPEOF, Id_var = Token.VAR, Id_void = Token.VOID, Id_while = Token.WHILE, Id_with = Token.WITH, // the following are #ifdef RESERVE_JAVA_KEYWORDS in jsscan.c Id_abstract = Token.RESERVED, Id_boolean = Token.RESERVED, Id_byte = Token.RESERVED, Id_catch = Token.CATCH, Id_char = Token.RESERVED, Id_class = Token.RESERVED, Id_const = Token.CONST, Id_debugger = Token.DEBUGGER, Id_double = Token.RESERVED, Id_enum = Token.RESERVED, Id_extends = Token.RESERVED, Id_final = Token.RESERVED, Id_finally = Token.FINALLY, Id_float = Token.RESERVED, Id_goto = Token.RESERVED, Id_implements = Token.RESERVED, Id_import = Token.IMPORT, Id_instanceof = Token.INSTANCEOF, Id_int = Token.RESERVED, Id_interface = Token.RESERVED, Id_long = Token.RESERVED, Id_native = Token.RESERVED, Id_package = Token.RESERVED, Id_private = Token.RESERVED, Id_protected = Token.RESERVED, Id_public = Token.RESERVED, Id_short = Token.RESERVED, Id_static = Token.RESERVED, Id_super = Token.RESERVED, Id_synchronized = Token.RESERVED, Id_throw = Token.THROW, Id_

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>(); escapeVal = Kit.xDigitToInt(c, 0); if (escapeVal < 0) { addToString('x'); continue strLoop; } else { int c1 = c; c = getChar(); escapeVal = Kit.xDigitToInt(c, escapeVal); if (escapeVal < 0) { addToString('x'); addToString(c1); continue strLoop; } else { // got 2 hex digits c = escapeVal; } } break; case '\n': // Remove line terminator after escape to follow // SpiderMonkey and C/C++ c = getChar(); continue strLoop; default: if ('0' <= c && c < '8') { int val = c - '0'; c = getChar(); if ('0' <= c && c < '8') { val = 8 * val + c - '0'; c = getChar(); if ('0' <= c && c < '8' && val <= 037) { // c is 3rd char of octal sequence only // if the resulting val <= 0377 val = 8 * val + c - '0'; c = getChar(); } } ungetChar(c); c = val; } } } addToString(c); c = getChar(); } String str = getStringFromBuffer(); this.string = (String)allStrings.intern(str); return Token.STRING; } switch (c) { case ';': return Token.SEMI; case '[': return Token.LB; case ']': return Token.RB; case '{': return Token.LC; case '}': return Token.RC; case '(': return Token.LP; case ')': return Token.RP; case ',': return Token.COMMA; case '?': return Token.HOOK; case ':': if (matchChar(':')) { return Token.COLONCOLON; } else { return Token.COLON; } case '.': if (matchChar('.')) { return Token.DOTDOT; } else if (matchChar('('))

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> { return Token.DOTQUERY; } else { return Token.DOT; } case '|': if (matchChar('|')) { return Token.OR; } else if (matchChar('=')) { return Token.ASSIGN_BITOR; } else { return Token.BITOR; } case '^': if (matchChar('=')) { return Token.ASSIGN_BITXOR; } else { return Token.BITXOR; } case '&': if (matchChar('&')) { return Token.AND; } else if (matchChar('=')) { return Token.ASSIGN_BITAND; } else { return Token.BITAND; } case '=': if (matchChar('=')) { if (matchChar('=')) return Token.SHEQ; else return Token.EQ; } else { return Token.ASSIGN; } case '!': if (matchChar('=')) { if (matchChar('=')) return Token.SHNE; else return Token.NE; } else { return Token.NOT; } case '<': /* NB:treat HTML begin-comment as comment-till-eol */ if (matchChar('!')) { if (matchChar('-')) { if (matchChar('-')) { skipLine(); continue retry; } ungetChar('-'); } ungetChar('!'); } if (matchChar('<')) { if (matchChar('=')) { return Token.ASSIGN_LSH; } else { return Token.LSH; } } else { if (matchChar('=')) { return Token.LE; } else { return Token.LT; } } case '>': if (matchChar('>')) { if (matchChar('>')) { if (matchChar('=')) { return Token.ASSIGN_URSH; } else { return Token.URSH; } } else { if (matchChar('=')) { return Token.ASSIGN_RSH; } else { return Token.RSH; } } } else { if (matchChar('=')) { return Token.GE; } else

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> declared names. Preconditions.checkState(parent.getType() != Token.FUNCTION || parent.getType() != Token.VAR || parent.getType() != Token.CATCH); // The name may need to be replaced more than once, // so we need to clone the node. Node replacement = replacementTemplate.cloneTree(); parent.replaceChild(node, replacement); return replacement; } } for (Node c = node.getFirstChild(); c != null; c = c.getNext()) { // We have to reassign c in case it was replaced, because the removed c's // getNext() would no longer be correct. c = inject(c, node, replacements); } return node; } /** * Get a mapping for function parameter names to call arguments. */ static LinkedHashMap<String, Node> getFunctionCallParameterMap( Node fnNode, Node callNode, Supplier<String> safeNameIdSupplier) { // Create an argName -> expression map // NOTE: A linked map is created here to provide ordering. LinkedHashMap<String, Node> argMap = Maps.newLinkedHashMap(); // CALL NODE: [ NAME, ARG1, ARG2, ... ] Node cArg = callNode.getFirstChild().getNext(); if (callNode.getFirstChild().getType() != Token.NAME) { if (NodeUtil.isFunctionObjectCall(callNode)) { // TODO(johnlenz): Support replace this with a value. Preconditions.checkNotNull(cArg); Preconditions.checkState(cArg.getType() == Token.THIS); cArg = cArg.getNext(); } else { Preconditions.checkState(!NodeUtil.isFunctionObjectApply(callNode)); } } for (Node fnArg : NodeUtil.getFnParameters(fnNode).children()) { if (cArg != null) { argMap.put(fnArg.getString(), cArg); cArg = cArg.getNext(); } else { argMap.put(fnArg.getString(), NodeUtil.newUndefinedNode()); } } // Add temp names for arguments that don't have named parameters in the // called function. int anonArg = 0; while (cArg != null) { String uniquePlaceholder = getUniqueAnonymousParameterName(safe

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>NameIdSupplier); argMap.put(uniquePlaceholder, cArg); cArg = cArg.getNext(); } return argMap; } /** * Parameter names will be name unique when at a later time. */ private static String getUniqueAnonymousParameterName( Supplier<String> safeNameIdSupplier) { return "JSCompiler_inline_anon_param_" + safeNameIdSupplier.get(); } /** * Retrieve a set of names that can not be safely substituted in place. * Example: * function(a) { * a = 0; * } * Inlining this without taking precautions would cause the call site value * to be modified (bad). */ static Set<String> findModifiedParameters(Node fnNode) { Set<String> names = getFunctionParameterSet(fnNode); Set<String> unsafeNames = Sets.newHashSet(); return findModifiedParameters( fnNode, null, names, unsafeNames); } /** * Check for uses of the named value that imply a pass-by-value * parameter is expected. This is used to prevent cases like: * * function (x) { * x=2; * return x; * } * * We don't want "undefined" to be substituted for "x", and get * undefined=2 * * @param n The node in question. * @param parent The parent of the node. * @param names The set of names to check. */ private static Set<String> findModifiedParameters( Node n, Node parent, Set<String> names, Set<String> unsafe) { Preconditions.checkArgument(unsafe != null); if (n.getType() == Token.NAME) { if (names.contains(n.getString())) { if (canNameValueChange(n, parent)) { unsafe.add(n.getString()); } } } for (Node c : n.children()) { findModifiedParameters(c, n, names, unsafe); } return unsafe; } /** * This is similar to NodeUtil.isLValue except that object properties and * array member modification aren't important ("o" in "

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>o.a = 2" is still "o" * after assignment, where in as "o = x", "o" is now "x"). * * This also looks for the redefinition of a name. * function (x){var x;} * * @param n The NAME node in question. * @param parent The parent of the node. */ private static boolean canNameValueChange(Node n, Node parent) { int type = parent.getType(); return (type == Token.VAR || type == Token.INC || type == Token.DEC || (NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == n)); } /** * Updates the set of parameter names in set unsafe to include any * arguments from the call site that require aliases. * @param fnNode The FUNCTION node to be inlined. * @param argMap The argument list for the call to fnNode. * @param namesNeedingTemps The set of names to update. */ static void maybeAddTempsForCallArguments( Node fnNode, Map<String, Node> argMap, Set<String> namesNeedingTemps, CodingConvention convention) { if (argMap.isEmpty()) { // No arguments to check, we are done. return; } Preconditions.checkArgument(fnNode.getType() == Token.FUNCTION); Node block = fnNode.getLastChild(); Set<String> parameters = argMap.keySet(); // Get the list of parameters that may need temporaries due to // side-effects. Set<String> namesAfterSideEffects = findParametersReferencedAfterSideEffect( parameters, block); // Check for arguments that are evaluated more than once. for (Map.Entry<String, Node> entry : argMap.entrySet()) { String argName = entry.getKey(); if (namesNeedingTemps.contains(argName)) { continue; } Node cArg = entry.getValue(); boolean safe = true; int references = NodeUtil.getNameReferenceCount(block, argName); if (NodeUtil.mayEffectMutableState(cArg) && references > 0) { // Note: Mutable arguments should be assigned to temps, as the // may be within in a loop: //

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>(); } } if (!sideEffectSeen) { // Look for side-effects. if (hasNonLocalSideEffect(n)) { sideEffectSeen = true; } } // If traversing the nodes of a loop save any references // that are seen. if (inLoop() || sideEffectSeen) { // Record references to parameters. if (n.getType() == Token.NAME) { String name = n.getString(); if (parameters.contains(name)) { parametersReferenced.add(name); } } } } /** * @return Whether the node may have non-local side-effects. */ private boolean hasNonLocalSideEffect(Node n) { boolean sideEffect = false; int type = n.getType(); // Note: Only care about changes to non-local names, specifically // ignore VAR declaration assignments. if (NodeUtil.isAssignmentOp(n) || type == Token.INC || type == Token.DEC) { Node lhs = n.getFirstChild(); // Ignore changes to local names. if (!isLocalName(lhs)) { sideEffect = true; } } else if (type == Token.CALL) { sideEffect = NodeUtil.functionCallHasSideEffects(n); } else if (type == Token.NEW) { sideEffect = NodeUtil.constructorCallHasSideEffects(n); } else if (type == Token.DELPROP) { sideEffect = true; } return sideEffect; } /** * @return Whether node is a reference to locally declared name. */ private boolean isLocalName(Node node) { if (NodeUtil.isName(node)) { String name = node.getString(); return locals.contains(name); } return false; } } /** * Gather any names declared in the local scope. */ private static void gatherLocalNames(Node n, Set<String> names) { Preconditions.checkState(n.getType() != Token.FUNCTION); if (n.getType() == Token.NAME) { switch (n.getParent().getType()) { case Token.VAR: case Token.CATCH: names.add(n.getString()); } }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> is relative only to the current * run of the CodeConsumer and will be normalized * later on by the SourceMap. * * @see SourceMap */ private static class Mapping { Node node; Position start; Position end; } /** * Starts the source mapping for the given * node at the current position. */ @Override void startSourceMapping(Node node) { if (createSrcMap && node.getProp(Node.SOURCEFILE_PROP) != null && node.getLineno() > 0) { int line = getCurrentLineIndex(); int index = getCurrentCharIndex(); // If the index is -1, we are not performing any mapping. if (index >= 0) { Mapping mapping = new Mapping(); mapping.node = node; mapping.start = new Position(line, index); mappings.push(mapping); allMappings.add(mapping); } } } /** * Finishes the source mapping for the given * node at the current position. */ @Override void endSourceMapping(Node node) { if (createSrcMap && node.getProp(Node.SOURCEFILE_PROP) != null && node.getLineno() > 0) { int line = getCurrentLineIndex(); int index = getCurrentCharIndex(); // If the index is -1, we are not performing any mapping. if (index >= 0) { Preconditions.checkState( !mappings.empty(), "Mismatch in start and end of mapping"); Mapping mapping = mappings.pop(); mapping.end = new Position(line, index); } } } /** * Generates the source map from the given code consumer, * appending the information it saved to the SourceMap * object given. */ @Override void generateSourceMap(SourceMap map){ if (createSrcMap) { for (Mapping mapping : allMappings) { map.addMapping(mapping.node, mapping.start, mapping.end); } } } /** * Reports to the code consumer that the given line has been cut at the * given position (i.e. a \n has been inserted there). All mappings in * the source maps after that position will be renormalized

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>: case Token.SHEQ: case Token.SHNE: case Token.CASE: Node left; Node right; if (operatorToken == Token.CASE) { left = condition.getParent().getFirstChild(); // the switch condition right = condition.getFirstChild(); } else { left = condition.getFirstChild(); right = condition.getLastChild(); } Node typeOfNode = null; Node stringNode = null; if (left.getType() == Token.TYPEOF && right.getType() == Token.STRING) { typeOfNode = left; stringNode = right; } else if (right.getType() == Token.TYPEOF && left.getType() == Token.STRING) { typeOfNode = right; stringNode = left; } if (typeOfNode != null && stringNode != null) { Node operandNode = typeOfNode.getFirstChild(); JSType operandType = getTypeIfRefinable(operandNode, blindScope); if (operandType != null) { boolean resultEqualsValue = operatorToken == Token.EQ || operatorToken == Token.SHEQ || operatorToken == Token.CASE; if (!outcome) { resultEqualsValue = !resultEqualsValue; } return caseTypeOf(operandNode, operandType, stringNode.getString(), resultEqualsValue, blindScope); } } } switch (operatorToken) { case Token.AND: if (outcome) { return caseAndOrNotShortCircuiting(condition.getFirstChild(), condition.getLastChild(), blindScope, true); } else { return caseAndOrMaybeShortCircuiting(condition.getFirstChild(), condition.getLastChild(), blindScope, true); } case Token.OR: if (!outcome) { return caseAndOrNotShortCircuiting(condition.getFirstChild(), condition.getLastChild(), blindScope, false); } else { return caseAndOrMaybeShortCircuiting(condition.getFirstChild(), condition.getLastChild(), blindScope, false); } case Token.EQ: if (outcome) { return caseEquality(condition, blindScope, EQ); } else { return caseEquality(condition, blindScope, NE); } case Token.NE: if (outcome) { return caseEquality

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>()) { case Token.FUNCTION: // this handles functions that are assigned to variables or // properties // e.g. goog.string.htmlEscape = function(str) { // } // get the function name and see if it's empty Node functionNameNode = n.getFirstChild(); String functionName = functionNameNode.getString(); if (functionName.length() == 0) { if (parent.getType() == Token.ASSIGN) { // this is an assignment to a property, generally either a // static function or a prototype function // e.g. goog.string.htmlEscape = function() { } or // goog.structs.Map.prototype.getCount = function() { } Node lhs = parent.getFirstChild(); String name = namer.getName(lhs); namer.setFunctionName(name, n); } else if (parent.getType() == Token.NAME) { // this is an assignment to a variable // e.g. var handler = function() {} String name = namer.getName(parent); namer.setFunctionName(name, n); } } break; case Token.ASSIGN: // this handles functions that are assigned to a prototype through // an object literal // e.g. BuzzApp.prototype = { // Start : function() { } // } Node lhs = n.getFirstChild(); Node rhs = lhs.getNext(); if (rhs.getType() == Token.OBJECTLIT) { nameObjectLiteralMethods(rhs, namer.getName(lhs)); } } } private void nameObjectLiteralMethods(Node objectLiteral, String context) { // Object literals are a list of key-value pairs. All object // literals produced by the parser have an even number of // children. Preconditions.checkState(objectLiteral.getChildCount() % 2 == 0); for (Node keyNode = objectLiteral.getFirstChild(); keyNode != null; keyNode = keyNode.getNext().getNext()) { // skip 2 for next key Node valueNode = keyNode.getNext(); // Object literal keys may be strings or numbers. Numbers are // skipped because name tokens may not start with a number. if (keyNode.getType() == Token

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>.STRING) { // concatenate the context and key name to get a new qualified name. String name = namer.getCombinedName(context, namer.getName(keyNode)); int type = valueNode.getType(); if (type == Token.FUNCTION) { // set name if function is anonymous Node functionNameNode = valueNode.getFirstChild(); String functionName = functionNameNode.getString(); if (functionName.isEmpty()) { namer.setFunctionName(name, valueNode); } } else if (type == Token.OBJECTLIT) { // process nested object literal nameObjectLiteralMethods(valueNode, name); } } } } }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>/* * Copyright 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.common.collect.Maps; import com.google.javascript.rhino.Node; import java.util.Map; /** * Memoize a scope creator. * * This allows you to make multiple passes, without worrying about * the expense of generating Scope objects over and over again. * * On the other hand, you also have to be more aware of what your passes * are doing. Scopes are memoized stupidly, so if the underlying tree * changes, the scope may be out of sync. * * @author nicksantos@google.com (Nick Santos) */ class MemoizedScopeCreator implements ScopeCreator { private final Map<Node, Scope> scopes = Maps.newHashMap(); private final ScopeCreator delegate; /** * @param delegate The real source of Scope objects. */ MemoizedScopeCreator(ScopeCreator delegate) { this.delegate = delegate; } @Override public Scope createScope(Node n, Scope parent) { Scope scope = scopes.get(n); if (scope == null) { scope = delegate.createScope(n, parent); scopes.put(n, scope); } else { Preconditions.checkState(parent == scope.getParent()); } return scope; } }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>edGraphNode<N, E> src = (LinkedDirectedGraphNode<N, E>) node; node = getDirectedGraphNode(destValue); if (node == null) { throw new IllegalArgumentException( destValue + " does not exist in graph"); } LinkedDirectedGraphNode<N, E> dest = (LinkedDirectedGraphNode<N, E>) node; LinkedDirectedGraphEdge<N, E> edge = new LinkedDirectedGraphEdge<N, E>(src, edgeValue, dest); src.getOutEdges().add(edge); dest.getInEdges().add(edge); return edge; } @Override public void disconnect(N n1, N n2) { disconnectInDirection(n1, n2); disconnectInDirection(n2, n1); } @Override public void disconnectInDirection(N srcValue, N destValue) { DiGraphNode<N, E> node = getDirectedGraphNode(srcValue); if (node == null) { throw new IllegalArgumentException( srcValue + " does not exist in graph"); } LinkedDirectedGraphNode<N, E> src = (LinkedDirectedGraphNode<N, E>) node; node = getDirectedGraphNode(destValue); if (node == null) { throw new IllegalArgumentException( destValue + " does not exist in graph"); } LinkedDirectedGraphNode<N, E> dest = (LinkedDirectedGraphNode<N, E>) node; for (DiGraphEdge<?, E> edge : getDirectedGraphEdges(srcValue, destValue)) { src.getOutEdges().remove(edge); dest.getInEdges().remove(edge); } } @Override public List<DiGraphNode<N, E>> getDirectedGraphNodes() { List<DiGraphNode<N, E>> nodeList = Lists.newArrayList(); nodeList.addAll(nodes.values()); return nodeList; } @Override public DiGraphNode<N, E> getDirectedGraphNode(N nodeValue) { return nodes.get(nodeValue); } @Override public GraphNode<N, E> getNode(N nodeValue) { return getDirectedGraphNode

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>(nodeValue); } @Override public List<DiGraphEdge<N, E>> getInEdges(N nodeValue) { LinkedDirectedGraphNode<N, E> node = nodes.get(nodeValue); if (node == null) { throw new IllegalArgumentException( nodeValue + " does not exist in graph"); } List<DiGraphEdge<N, E>> edgeList = Lists.newArrayList(); for (DiGraphEdge<N, E> edge : node.getInEdges()) { edgeList.add(edge); } return edgeList; } @Override public List<DiGraphEdge<N, E>> getOutEdges(N nodeValue) { LinkedDirectedGraphNode<N, E> node = nodes.get(nodeValue); if (node == null) { throw new IllegalArgumentException( nodeValue + " does not exist in graph"); } List<DiGraphEdge<N, E>> edgeList = Lists.newArrayList(); for (DiGraphEdge<N, E> edge : node.getOutEdges()) { edgeList.add(edge); } return edgeList; } @Override public DiGraphNode<N, E> createDirectedGraphNode(N nodeValue) { LinkedDirectedGraphNode<N, E> node = nodes.get(nodeValue); if (node == null) { node = new LinkedDirectedGraphNode<N, E>(nodeValue); nodes.put(nodeValue, node); } return node; } @Override public List<GraphEdge<N, E>> getEdges(N n1, N n2) { // Since this is a method from a generic graph, edges from both // directions must be added to the returning list. List<DiGraphEdge<N, E>> forwardEdges = getDirectedGraphEdges(n1, n2); List<DiGraphEdge<N, E>> backwardEdges = getDirectedGraphEdges(n2, n1); int totalSize = forwardEdges.size() + backwardEdges.size(); List<GraphEdge<N, E>> edges = Lists.newArrayListWithCapacity(totalSize); edges.addAll(forwardEdges); edges.addAll(backwardEdges); return edges; } @Override

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS><N, E> dNode) { if (dNode == null) { throw new IllegalArgumentException(dNode + " is null"); } List<DiGraphNode<N, E>> nodeList = Lists.newArrayList(); for (DiGraphEdge<N, E> edge : dNode.getInEdges()) { nodeList.add(edge.getSource()); } return nodeList; } @Override public List<DiGraphNode<N, E>> getDirectedSuccNodes( DiGraphNode<N, E> dNode) { if (dNode == null) { throw new IllegalArgumentException(dNode + " is null"); } List<DiGraphNode<N, E>> nodeList = Lists.newArrayList(); for (DiGraphEdge<N, E> edge : dNode.getOutEdges()) { nodeList.add(edge.getDestination()); } return nodeList; } @Override public boolean isConnected(N n1, N n2) { return isConnectedInDirection(n1, n2) || isConnectedInDirection(n2, n1); } @Override public List<GraphvizEdge> getGraphvizEdges() { List<GraphvizEdge> edgeList = Lists.newArrayList(); for (LinkedDirectedGraphNode<N, E> node : nodes.values()) { for (DiGraphEdge<N, E> edge : node.getOutEdges()) { edgeList.add((LinkedDirectedGraphEdge<N, E>) edge); } } return edgeList; } @Override public List<GraphvizNode> getGraphvizNodes() { List<GraphvizNode> nodeList = Lists.newArrayListWithCapacity(nodes.size()); for (LinkedDirectedGraphNode<N, E> node : nodes.values()) { nodeList.add(node); } return nodeList; } @Override public String getName() { return "LinkedGraph"; } @Override public boolean isDirected() { return true; } @Override public List<GraphNode<N, E>> getNodes() { List<GraphNode<N, E>> list = Lists.newArrayList(); list.addAll(nodes.values()); return list; }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> @Override public List<GraphNode<N, E>> getNeighborNodes(N value) { DiGraphNode<N, E> node = getDirectedGraphNode(value); return getNeighborNodes(node); } public List<GraphNode<N, E>> getNeighborNodes(DiGraphNode<N, E> node) { List<GraphNode<N, E>> result = Lists.newArrayList(); for (Iterator<GraphNode<N, E>> i = ((LinkedDirectedGraphNode<N, E>) node).neighborIterator();i.hasNext();) { result.add(i.next()); } return result; } @Override public Iterator<GraphNode<N, E>> getNeighborNodesIterator(N value) { LinkedDirectedGraphNode<N, E> node = nodes.get(value); Preconditions.checkNotNull(node); return node.neighborIterator(); } @Override public List<GraphEdge<N, E>> getEdges() { List<GraphEdge<N, E>> result = Lists.newArrayList(); for (DiGraphNode<N, E> node : nodes.values()) { for (DiGraphEdge<N, E> edge : node.getOutEdges()) { result.add(edge); } } return result; } @Override public int getNodeDegree(N value) { DiGraphNode<N, E> node = getDirectedGraphNode(value); if (node == null) { throw new IllegalArgumentException(value + " not found in graph"); } return node.getInEdges().size() + node.getOutEdges().size(); } /** * A directed graph node that stores outgoing edges and incoming edges as an * list within the node itself. */ static class LinkedDirectedGraphNode<N, E> implements DiGraphNode<N, E>, GraphvizNode { protected List<DiGraphEdge<N, E>> inEdgeList = Lists.newArrayList(); protected List<DiGraphEdge<N, E>> outEdgeList = Lists.newArrayList(); protected final N value; protected Annotation annotation; protected int id; private static int totalNodes = 0; /** * Constructor * * @param nodeValue Node's value. */

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> * "directly" inlined functions must meet these additional requirements: * - consists of a single return statement * * * @author johnlenz@google.com (John Lenz) */ class InlineFunctions implements CompilerPass { // TODO(nicksantos): This needs to be completely rewritten to use scopes // to do variable lookups. Right now, it assumes that all functions are // uniquely named variables. There's currently a stopgap scope-check // to ensure that this doesn't produce invalid code. But in the long run, // this needs a major refactor. private final Map<String, FunctionState> fns = Maps.newHashMap(); private final Map<Node, String> anonFns = Maps.newHashMap(); private final AbstractCompiler compiler; private final FunctionInjector injector; private final boolean blockFunctionInliningEnabled; private final boolean inlineAnonymousFunctionExpressions; private final boolean inlineGlobalFunctions; private final boolean inlineLocalFunctions; InlineFunctions(AbstractCompiler compiler, Supplier<String> safeNameIdSupplier, boolean inlineGlobalFunctions, boolean inlineLocalFunctions, boolean inlineAnonymousFunctionExpressions, boolean blockFunctionInliningEnabled, boolean enableExpressionDecomposition) { Preconditions.checkArgument(compiler != null); Preconditions.checkArgument(safeNameIdSupplier != null); this.compiler = compiler; this.inlineGlobalFunctions = inlineGlobalFunctions; this.inlineLocalFunctions = inlineLocalFunctions; this.inlineAnonymousFunctionExpressions = inlineAnonymousFunctionExpressions; this.blockFunctionInliningEnabled = blockFunctionInliningEnabled; this.injector = new FunctionInjector( compiler, safeNameIdSupplier, enableExpressionDecomposition); } FunctionState getOrCreateFunctionState(String fnName) { FunctionState fs = fns.get(fnName); if (fs == null) { fs = new FunctionState(); fns.put(fnName, fs); } return fs; } /** * {@inheritDoc} */ public void process(Node externs, Node root) { Preconditions.checkState(compiler.isNormalized()); NodeTraversal.traverse(compiler, root, new FindCandidateFunctions()); if (fns.isEmpty()) { return; // Nothing left to do. } NodeTraversal.traverse(compiler, root,

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>): Make this a Preconditions check. // Currently this fails for some targets. if (n.hasOneChild()) { // Only look at declarations in the global scope. Node nameNode = n.getFirstChild(); if (nameNode.getType() == Token.NAME && nameNode.hasChildren() && nameNode.getFirstChild().getType() == Token.FUNCTION) { maybeAddFunction(new FunctionVar(n), t.getModule()); } } break; // Named functions // function Foo(x) { return ... } case Token.FUNCTION: Preconditions.checkState(NodeUtil.isStatementBlock(parent) || parent.getType() == Token.LABEL); Function fn = new NamedFunction(n); String name = fn.getName(); if (!name.isEmpty()) { maybeAddFunction(fn, t.getModule()); } break; } } /** * Find anonymous functions that are called directly in the form of * (function(a,b,...){...})(a,b,...) * or * (function(a,b,...){...}).call(this,a,b, ...) */ public void findAnonymousFunctionExpressions(NodeTraversal t, Node n) { switch (n.getType()) { // Anonymous functions in the form of: // (function(){})(); case Token.CALL: Node fnNode = null; if (n.getFirstChild().getType() == Token.FUNCTION) { fnNode = n.getFirstChild(); } else if (NodeUtil.isFunctionObjectCall(n)) { Node fnIdentifingNode = n.getFirstChild().getFirstChild(); if (fnIdentifingNode.getType() == Token.FUNCTION) { fnNode = fnIdentifingNode; } } // If a interesting function was discovered, add it. if (fnNode != null) { Function fn = new AnonymousFunction(fnNode, callsSeen++); maybeAddFunction(fn, t.getModule()); anonFns.put(fnNode, fn.getName()); } break; } } } /** * Updates the FunctionState object for the given function. Checks if the * given function matches the criteria for an inlinable function. */ private void maybeAddFunction

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> name can be replaced externally, any inlined instance // would not reflect this change. // To allow inlining we need to be able to distinguish between exports // that are used in a read-only fashion and those that can be replaced // by external definitions. return false; } // Don't inline this special function if (RenameProperties.RENAME_PROPERTY_FUNCTION_NAME.equals(fnName)) { return false; } Node fnNode = fn.getFunctionNode(); return injector.doesFunctionMeetMinimumRequirements(fnName, fnNode); } /** * @see CallVisitor */ private interface CallVisitorCallback { public void visitCallSite( NodeTraversal t, Node callNode, Node parent, FunctionState fs); } /** * Visit call sites for functions in functionMap. */ private static class CallVisitor extends AbstractPostOrderCallback { protected CallVisitorCallback callback; private Map<String, FunctionState> functionMap; private Map<Node, String> anonFunctionMap; CallVisitor(Map<String, FunctionState> fns, Map<Node, String> anonFns, CallVisitorCallback callback) { this.functionMap = fns; this.anonFunctionMap = anonFns; this.callback = callback; } public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { // Function calls case Token.CALL: Node child = n.getFirstChild(); String name = null; // NOTE: The normalization pass insures that local names do not // collide with global names. if (child.getType() == Token.NAME) { name = child.getString(); } else if (child.getType() == Token.FUNCTION) { name = anonFunctionMap.get(child); } else if (NodeUtil.isFunctionObjectCall(n)) { Preconditions.checkState(NodeUtil.isGet(child)); Node fnIdentifingNode = child.getFirstChild(); if (fnIdentifingNode.getType() == Token.NAME) { name = fnIdentifingNode.getString(); } else if (fnIdentifingNode.getType() == Token.FUNCTION) { name = anonFunctionMap.get(fnIdent

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>canInlineReferenceToFunction( t, callNode, fs.getFn().getFunctionNode(), fs.getNamesToAlias(), mode, fs.getReferencesThis()); if (result != CanInlineResult.NO) { // Yeah! boolean decompose = (result == CanInlineResult.AFTER_DECOMPOSITION); fs.addReference(new Reference(callNode, module, mode, decompose)); return true; } return false; } /** * Find functions that can be inlined. */ private void checkNameUsage(NodeTraversal t, Node n, Node parent) { Preconditions.checkState(n.getType() == Token.NAME); if (parent.getType() == Token.VAR || parent.getType() == Token.FUNCTION) { // This is a declaration. Duplicate declarations are handle during // function candidate gathering. return; } if (parent.getType() == Token.CALL && parent.getFirstChild() == n) { // This is a normal reference to the function. return; } // Check for a ".call" to the named function: // CALL // GETPROP/GETELEM // NAME // STRING == "call" // This-Value // Function-parameter-1 // ... if (NodeUtil.isGet(parent) && n == parent.getFirstChild() && n.getNext().getType() == Token.STRING && n.getNext().getString().equals("call")) { Node gramps = n.getAncestor(2); if (gramps.getType() == Token.CALL && gramps.getFirstChild() == parent) { // Yep, a ".call". return; } } // Other refs to a function name remove its candidacy for inlining String name = n.getString(); FunctionState fs = fns.get(name); if (fs == null) { return; } // If the name is being assigned to it can not be inlined. if (parent.getType() == Token.ASSIGN && parent.getFirstChild() == n) { // e.g. bar = something; <== we can't inline "bar" // so mark the function as uninlinable. //

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> TODO(johnlenz): Should we just remove it from fns here? fs.setInline(false); } else { // e.g. var fn = bar; <== we can't inline "bar" // As this reference can't be inlined mark the function as // unremovable. fs.setRemove(false); } } } /** * Inline functions at the call sites. */ private static class Inline implements CallVisitorCallback { private final FunctionInjector injector; Inline(FunctionInjector injector) { this.injector = injector; } public void visitCallSite( NodeTraversal t, Node callNode, Node parent, FunctionState fs) { Preconditions.checkState(fs.hasExistingFunctionDefinition()); if (fs.canInline()) { Reference ref = fs.getReference(callNode); // There are two cases ref can be null: if the call site was introduce // because it was part of a function that was inlined during this pass // or if the call site was trimmed from the list of references because // the function couldn't be inlined at this location. if (ref != null) { inlineFunction(t, callNode, fs, ref.mode); // Keep track of references that have been inlined so that // we can verify that none have been missed. ref.inlined = true; } } } /** * Inline a function into the call site. */ private void inlineFunction( NodeTraversal t, Node callNode, FunctionState fs, InliningMode mode) { Function fn = fs.getFn(); String fnName = fn.getName(); Node fnNode = fs.getSafeFnNode(); Node newCode = injector.inline(t, callNode, fnName, fnNode, mode); t.getCompiler().reportCodeChange(); t.getCompiler().addToDebugLog("Inlined function: " + fn.getName()); } } /** * Remove entries that aren't a valid inline candidates, from the list of * encountered names. */ private void trimCanidatesNotMeetingMinimumRequirements() { Iterator<Entry<String, FunctionState>> i; for (i = fns.entrySet().iterator(); i.hasNext();) { FunctionState fs = i.

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> in the list of function * references so they won't be inlined (which is what we want). Here we mark * those functions as non-removable (as they will have new references in the * cloned node trees). * * This prevents a function that would only be inlined because it is * referenced once from being inlined into multiple call sites because * the calling function has been inlined in multiple locations or the * function being removed while there are still references. */ private void resolveInlineConflicts() { for (FunctionState fs : fns.values()) { resolveInlineConflictsForFunction(fs); } } /** * @see #resolveInlineConflicts */ private void resolveInlineConflictsForFunction(FunctionState fs) { // Functions that aren't referenced don't cause conflicts. if (!fs.hasReferences()) { return; } Node fnNode = fs.getFn().getFunctionNode(); Set<String> names = findCalledFunctions(fnNode); if (!names.isEmpty()) { // Prevent the removal of the referenced functions. for (String name : names) { FunctionState fsCalled = fns.get(name); if (fsCalled != null && fsCalled.canRemove()) { fsCalled.setRemove(false); // For functions that can no longer be removed, check if they should // still be inlined. if (!mimimizeCost(fsCalled)) { // It can't be inlined remove it from the list. fsCalled.setInline(false); } } } // Make a copy of the Node, so it isn't changed by other inlines. fs.setSafeFnNode(fs.getFn().getFunctionNode().cloneTree()); } } /** * This functions that may be called directly. */ private Set<String> findCalledFunctions(Node node) { Set<String> changed = Sets.newHashSet(); findCalledFunctions(node, changed); return changed; } /** * @see #findCalledFunctions(Node) */ private void findCalledFunctions( Node node, Set<String> changed) { Preconditions.checkArgument(changed != null); // For each referenced function, add a new reference if (node.

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>getType() == Token.CALL) { Node child = node.getFirstChild(); if (child.getType() == Token.NAME) { String name = child.getString(); changed.add(name); } } for (Node c = node.getFirstChild(); c != null; c = c.getNext()) { findCalledFunctions(c, changed); } } /** * For any call-site that needs it, prepare the call-site for inlining * by rewriting the containing expression. */ private void decomposeExpressions(Set<String> fnNames) { ExpressionDecomposer decomposer = new ExpressionDecomposer( compiler, compiler.getUniqueNameIdSupplier(), fnNames); for (FunctionState fs : fns.values()) { if (fs.canInline()) { for (Reference ref : fs.getReferences()) { if (ref.requiresDecomposition) { decomposer.maybeDecomposeExpression(ref.callNode); } } } } } /** * Removed inlined functions that no longer have any references. */ void removeInlinedFunctions() { for (FunctionState fs : fns.values()) { if (fs.canRemove()) { Function fn = fs.getFn(); Preconditions.checkState(fs.canInline()); Preconditions.checkState(fn != null); verifyAllReferencesInlined(fs); fn.remove(); compiler.reportCodeChange(); } } } /** * Sanity check to verify, that expression rewriting didn't * make a call inaccessible. */ void verifyAllReferencesInlined(FunctionState fs) { for (Reference ref : fs.getReferences()) { if (!ref.inlined) { throw new IllegalStateException("Call site missed."); } } } /** * Use to track the decisions that have been make about a function. */ private static class FunctionState { private Function fn = null; private Node safeFnNode = null; private boolean inline = true; private boolean remove = true; private boolean inlineDirectly = false; private boolean referencesThis = false; private Map<Node, Reference> references = null; private JSModule module = null; private Set<String> namesToAlias = null;

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> boolean hasExistingFunctionDefinition() { return (fn != null); } public void setReferencesThis(boolean referencesThis) { this.referencesThis = referencesThis; } public boolean getReferencesThis() { return this.referencesThis; } void removeBlockInliningReferences() { Iterator<Entry<Node, Reference>> i; for (i = getReferencesInternal().entrySet().iterator(); i.hasNext();) { Entry<Node, Reference> entry = i.next(); if (entry.getValue().mode == InliningMode.BLOCK) { i.remove(); } } } public boolean hasBlockInliningReferences() { for (Reference r : getReferencesInternal().values()) { if (r.mode == InliningMode.BLOCK) { return true; } } return false; } public Function getFn() { return fn; } public void setFn(Function fn) { Preconditions.checkState(this.fn == null); this.fn = fn; } public Node getSafeFnNode() { return (safeFnNode != null) ? safeFnNode : fn.getFunctionNode(); } public void setSafeFnNode(Node safeFnNode) { this.safeFnNode = safeFnNode; } public boolean canInline() { return inline; } public void setInline(boolean inline) { this.inline = inline; if (inline == false) { // No need to keep references to function that can't be inlined. references = null; // Don't remove functions that we aren't inlining. remove = false; } } public boolean canRemove() { return remove; } public void setRemove(boolean remove) { this.remove = remove; } public boolean canInlineDirectly() { return inlineDirectly; } public void inlineDirectly(boolean directReplacement) { this.inlineDirectly = directReplacement; } public boolean hasReferences() { return (references != null && !references.isEmpty()); } private Map<Node, Reference> getReferencesInternal() { if (references == null) { return Collections.emptyMap(); } return references; } public void addReference(Reference ref) {

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>/* * Copyright 2008 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.common.base.Supplier; import com.google.common.collect.Sets; import com.google.javascript.jscomp.ExpressionDecomposer.DecompositionType; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.util.Collection; import java.util.Map; import java.util.Set; /** * A set of utility functions that replaces CALL with a specified * FUNCTION body, replacing and aliasing function parameters as * necessary. * * @author johnlenz@google.com (John Lenz) */ class FunctionInjector { private final AbstractCompiler compiler; private final Supplier<String> safeNameIdSupplier; private final boolean allowDecomposition; private Set<String> knownConstants = Sets.newHashSet(); /** * @param allowDecomposition Whether an effort should be made to break down * expressions into simpler expressions to allow functions to be injected * where they would otherwise be disallowed. */ public FunctionInjector( AbstractCompiler compiler, Supplier<String> safeNameIdSupplier, boolean allowDecomposition) { Preconditions.checkNotNull(compiler); Preconditions.checkNotNull(safeNameIdSupplier); this.compiler = compiler; this.safeNameIdSupplier = safeNameIdSupplier; this.allowDecomposition = allowDecomposition; } /** The type of inlining to perform. */ enum InliningMode { /** * Directly replace the call expression. Only functions

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>isFunctionObjectApply(callNode)) { return false; } } return true; } /** * Inline a function into the call site. */ Node inline( NodeTraversal t, Node callNode, String fnName, Node fnNode, InliningMode mode) { Preconditions.checkState(compiler.isNormalized()); if (mode == InliningMode.DIRECT) { return inlineReturnValue(callNode, fnNode); } else { return inlineFunction(callNode, fnNode, fnName); } } /** * Inline a function that fulfills the requirements of * canInlineReferenceDirectly into the call site, replacing only the CALL * node. */ private Node inlineReturnValue(Node callNode, Node fnNode) { Node block = fnNode.getLastChild(); Node callParentNode = callNode.getParent(); // NOTE: As the normalize pass guarantees globals aren't being // shadowed and an expression can't introduce new names, there is // no need to check for conflicts. // Create an argName -> expression map, checking for side effects. Map<String, Node> argMap = FunctionArgumentInjector.getFunctionCallParameterMap( fnNode, callNode, this.safeNameIdSupplier); Node newExpression; if (!block.hasChildren()) { newExpression = NodeUtil.newUndefinedNode(); } else { Node returnNode = block.getFirstChild(); Preconditions.checkArgument(returnNode.getType() == Token.RETURN); // Clone the return node first. Node safeReturnNode = returnNode.cloneTree(); Node inlineResult = FunctionArgumentInjector.inject( safeReturnNode, null, argMap); Preconditions.checkArgument(safeReturnNode == inlineResult); newExpression = safeReturnNode.removeFirstChild(); } callParentNode.replaceChild(callNode, newExpression); return newExpression; } /** * Supported call site types. */ private enum CallSiteType { /** * Used for a call site for which there does not exist a method * to inline it. */ UNSUPPORTED, /** * A call as a statement. For example: "foo();". * EXPR_RESULT * CALL */ SIMPLE_CALL,

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> !NodeUtil.isConstantName(parent) && grandParent.getType() == Token.VAR && grandParent.hasOneChild()) { // This is a var declaration. Example: "var x = foo();" // TODO(johnlenz): Should we be checking for constants on the // left-hand-side of the assignments (and handling them as EXPRESSION? return CallSiteType.VAR_DECL_SIMPLE_ASSIGNMENT; } else { Node expressionRoot = ExpressionDecomposer.findExpressionRoot(callNode); if (expressionRoot != null) { ExpressionDecomposer decomposer = new ExpressionDecomposer( compiler, safeNameIdSupplier, knownConstants); DecompositionType type = decomposer.canExposeExpression( callNode); if (type == DecompositionType.MOVABLE) { return CallSiteType.EXPRESSION; } else if (type == DecompositionType.DECOMPOSABLE) { return CallSiteType.DECOMPOSABLE_EXPRESSION; } else { Preconditions.checkState(type == DecompositionType.UNDECOMPOSABLE); } } } return CallSiteType.UNSUPPORTED; } /** * Inline a function which fulfills the requirements of * canInlineReferenceAsStatementBlock into the call site, replacing the * parent expression. */ private Node inlineFunction( Node callNode, Node fnNode, String fnName) { Node parent = callNode.getParent(); Node grandParent = parent.getParent(); // TODO(johnlenz): Consider storing the callSite classification in the // reference object and passing it in here. CallSiteType callSiteType = classifyCallSite(callNode); Preconditions.checkArgument(callSiteType != CallSiteType.UNSUPPORTED); // Store the name for the result. This will be used to // replace "return expr" statements with "resultName = expr" // to replace String resultName = null; boolean needsDefaultReturnResult = true; switch (callSiteType) { case SIMPLE_ASSIGNMENT: resultName = parent.getFirstChild().getString(); break; case VAR_DECL_SIMPLE_ASSIGNMENT: resultName = parent.getString(); break; case SIMPLE_CALL: resultName = null; // "foo()" doesn't need

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> a result. needsDefaultReturnResult = false; break; case EXPRESSION: resultName = getUniqueResultName(); needsDefaultReturnResult = false; // The intermediary result already // has the default value. break; case DECOMPOSABLE_EXPRESSION: throw new IllegalStateException( "Decomposable expressions must decomposed before inlining."); default: throw new IllegalStateException("Unexpected call site type."); } boolean isCallInLoop = isCallWithinLoop(callNode); FunctionToBlockMutator mutator = new FunctionToBlockMutator( compiler, this.safeNameIdSupplier); Node newBlock = mutator.mutate( fnName, fnNode, callNode, resultName, needsDefaultReturnResult, isCallInLoop); // TODO(nicksantos): Create a common mutation function that // can replace either a VAR name assignment, assignment expression or // a EXPR_RESULT. Node greatGrandParent = grandParent.getParent(); switch (callSiteType) { case VAR_DECL_SIMPLE_ASSIGNMENT: // Remove the call from the name node. parent.removeChild(parent.getFirstChild()); Preconditions.checkState(parent.getFirstChild() == null); // Add the call, after the VAR. greatGrandParent.addChildAfter(newBlock, grandParent); break; case SIMPLE_ASSIGNMENT: // The assignment is now part of the inline function so // replace it completely. Preconditions.checkState(NodeUtil.isExpressionNode(grandParent)); greatGrandParent.replaceChild(grandParent, newBlock); break; case SIMPLE_CALL: // If nothing is looking at the result just replace the call. Preconditions.checkState(NodeUtil.isExpressionNode(parent)); grandParent.replaceChild(parent, newBlock); break; case EXPRESSION: // TODO(johnlenz): Maybe change this so that movable and decomposable // expressions are handled the same way: The call is moved and // then handled by one the three basic cases, rather than // introducing a new case. Node injectionPoint = ExpressionDecomposer.findInjectionPoint(callNode); Preconditions.checkNotNull(injectionPoint); Node injectionPointParent = injectionPoint.getParent(); Preconditions.checkNotNull(injection

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>PointParent); Preconditions.checkState( NodeUtil.isStatementBlock(injectionPointParent)); // Declare the intermediate result name. newBlock.addChildrenToFront(NodeUtil.newVarNode(resultName, null)); // Inline the function before the selected injection point (before // the call). injectionPointParent.addChildBefore(newBlock, injectionPoint); // Replace the call site with a reference to the intermediate // result name. parent.replaceChild(callNode, Node.newString(Token.NAME, resultName)); break; default: throw new IllegalStateException("Unexpected call site type."); } return newBlock; } /** * @return Whether the specified callNode has a loop parent that * is within the current scope. */ private boolean isCallWithinLoop(Node callNode) { for (Node parent : callNode.getAncestors()) { if (NodeUtil.isLoopStructure(parent)) { return true; } if (NodeUtil.isFunction(parent)) { break; } } return false; } /** * Checks if the given function matches the criteria for an inlinable * function, and if so, adds it to our set of inlinable functions. */ boolean isDirectCallNodeReplacementPossible(Node fnNode) { // Only inline single-statement functions Node block = NodeUtil.getFunctionBody(fnNode); // Check if this function is suitable for direct replacement of a CALL node: // a function that consists of single return that returns an expression. if (!block.hasChildren()) { // special case empty functions. return true; } else if (block.hasOneChild()) { // Only inline functions that return something. if (block.getFirstChild().getType() == Token.RETURN && block.getFirstChild().getFirstChild() != null) { return true; } } return false; } enum CanInlineResult { YES, AFTER_DECOMPOSITION, NO } /** * Determines whether a function can be inlined at a particular call site. * There are several criteria that the function and reference must hold in * order for the functions to be inlined: * - It must be a simple call, or assignment, or var initialization. *

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> we aren't adding any // additional VAR declarations because aliasing is needed. if (callerContainsFunction) { Map<String, Node> args = FunctionArgumentInjector.getFunctionCallParameterMap( fnNode, callNode, this.safeNameIdSupplier); boolean hasArgs = !args.isEmpty(); if (hasArgs) { // Limit the inlining Set<String> allNamesToAlias = Sets.newHashSet(namesToAlias); FunctionArgumentInjector.maybeAddTempsForCallArguments( fnNode, args, allNamesToAlias, compiler.getCodingConvention()); if (!allNamesToAlias.isEmpty()) { return false; } } } return true; } /** * Determines whether a function can be inlined at a particular call site. * There are several criteria that the function and reference must hold in * order for the functions to be inlined: * 1) If a call's arguments have side effects, * the corresponding argument in the function must only be referenced once. * For instance, this will not be inlined: * <pre> * function foo(a) { return a + a } * x = foo(i++); * </pre> */ private CanInlineResult canInlineReferenceDirectly( Node callNode, Node fnNode) { if (!isDirectCallNodeReplacementPossible(fnNode)) { return CanInlineResult.NO; } Node block = fnNode.getLastChild(); // CALL NODE: [ NAME, ARG1, ARG2, ... ] Node cArg = callNode.getFirstChild().getNext(); // Functions called via 'call' and 'apply' have a this-object as // the first parameter, but this is not part of the called function's // parameter list. if (callNode.getFirstChild().getType() != Token.NAME) { if (NodeUtil.isFunctionObjectCall(callNode)) { // TODO(johnlenz): Support replace this with a value. Preconditions.checkNotNull(cArg); Preconditions.checkState(cArg.getType() == Token.THIS); cArg = cArg.getNext(); } else { // ".apply" call should be filtered before this. Preconditions.checkState(!NodeUtil.isFunctionObjectApply(callNode

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> is zero for empty functions. return -costDeltaFunctionOverhead; } if (mode == InliningMode.DIRECT) { // The part of the function that is inlined using direct inlining: // "return " (7) return -(costDeltaFunctionOverhead + 7); } else { int aliasCount = namesToAlias.size(); // Originally, we estimated purely base on the function code size, relying // on later optimizations. But that did not produce good results, so here // we try to estimate the something closer to the actual inlined coded. // NOTE 1: Result overhead is only if there is an assignment, but // getting that information would require some refactoring. // NOTE 2: The aliasing overhead is currently an under-estimate, // as some parameters are aliased because of the parameters used. // Perhaps we should just assume all parameters will be aliased? final int INLINE_BLOCK_OVERHEAD = 4; // "X:{}" final int PER_RETURN_OVERHEAD = 2; // "return" --> "break X" final int PER_RETURN_RESULT_OVERHEAD = 3; // "XX=" final int PER_ALIAS_OVERHEAD = 3; // "XX=" // TODO(johnlenz): Counting the number of returns is relatively expensive // this information should be determined during the traversal and // cached. int returnCount = NodeUtil.getNodeTypeReferenceCount(block, Token.RETURN); int resultCount = (returnCount > 0) ? returnCount - 1 : 0; int baseOverhead = (returnCount > 0) ? INLINE_BLOCK_OVERHEAD : 0; int overhead = baseOverhead + returnCount * PER_RETURN_OVERHEAD + resultCount * PER_RETURN_RESULT_OVERHEAD + aliasCount * PER_ALIAS_OVERHEAD; return (overhead - costDeltaFunctionOverhead); } } /** * Store the names of known constants to be used when classifying call-sites * in expressions. */ public void setKnownConstants(Set<String> knownConstants) { // This is only expected to be set once. The same set should be used // when evaluating call-sites and inlining calls. Preconditions

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>.checkState(this.knownConstants.isEmpty()); this.knownConstants = knownConstants; } }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>/* * Copyright 2008 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.JSTypeExpression; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; /** * Prepare the AST before we do any checks or optimizations on it. * * This pass must run. It should bring the AST into a consistent state, * and add annotations where necessary. It should not make any transformations * on the tree that would lose source information, since we need that source * information for checks. * * @author johnlenz@google.com (John Lenz) */ class PrepareAst implements CompilerPass { private final AbstractCompiler compiler; private final boolean assertOnChange; PrepareAst(AbstractCompiler compiler) { this(compiler, false); } PrepareAst(AbstractCompiler compiler, boolean forbidChanges) { this.compiler = compiler; this.assertOnChange = forbidChanges; } private void reportChange() { if (assertOnChange) { Preconditions.checkState(false, "normalizeNodeType constraints violated"); } } @Override public void process(Node externs, Node root) { normalizeNodeTypes(root); if (externs != null) { NodeTraversal.traverse( compiler, externs, new PrepareAnnotations(compiler)); } if (root != null) { NodeTraversal.traverse( compiler, root, new PrepareAnnotations(compiler)); } } /**

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> * Covert EXPR_VOID to EXPR_RESULT to simplify the rest of the code. */ private void normalizeNodeTypes(Node n) { if (n.getType() == Token.EXPR_VOID) { n.setType(Token.EXPR_RESULT); reportChange(); } // Remove unused properties to minimize differences between ASTs // produced by the two parsers. if (n.getType() == Token.FUNCTION) { Preconditions.checkState(n.getProp(Node.FUNCTION_PROP) == null); } normalizeBlocks(n); for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { // This pass is run during the CompilerTestCase validation, so this // parent pointer check serves as a more general check. Preconditions.checkState(child.getParent() == n); normalizeNodeTypes(child); } } /** * Add blocks to IF, WHILE, DO, etc. */ private void normalizeBlocks(Node n) { if (NodeUtil.isControlStructure(n) && n.getType() != Token.LABEL && n.getType() != Token.SWITCH) { for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (NodeUtil.isControlStructureCodeBlock(n,c) && c.getType() != Token.BLOCK) { Node newBlock = new Node(Token.BLOCK); newBlock.copyInformationFrom(n); n.replaceChild(c, newBlock); if (c.getType() != Token.EMPTY) { newBlock.addChildrenToFront(c); } else { newBlock.setWasEmptyNode(true); } c = newBlock; reportChange(); } } } } /** * Normalize where annotations appear on the AST. Copies * around existing JSDoc annotations as well as internal annotations. */ static class PrepareAnnotations extends NodeTraversal.AbstractPostOrderCallback { private final AbstractCompiler compiler; private final CodingConvention convention; PrepareAnnotations(AbstractCompiler compiler) { this.compiler = compiler; this.convention = compiler.getCodingConvention(); } /** * * In the AST that Rhino gives us, it needs

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> to make a distinction * between jsdoc on the object literal node and jsdoc on the object literal * value. For example, * <pre> * var x = { * / JSDOC / * a: 'b', * c: / JSDOC / 'd' * }; * </pre> * * But in few narrow cases (in particular, function literals), it's * a lot easier for us if the doc is attached to the value. */ public void visit(NodeTraversal t, Node n, Node parent) { int nType = n.getType(); switch (nType) { case Token.NAME: case Token.STRING: String nString = n.getString(); if (nType == Token.NAME && n.getParent().getType() == Token.CALL && "eval".equals(nString)) { n.putBooleanProp(Node.DIRECT_EVAL, true); } if (convention.isConstant(nString)) { n.putBooleanProp(Node.IS_CONSTANT_NAME, true); } break; case Token.FUNCTION: JSDocInfo fnInfo = n.getJSDocInfo(); if (fnInfo == null) { // Look for the info on other nodes. if (parent.getType() == Token.ASSIGN) { // on ASSIGNs fnInfo = parent.getJSDocInfo(); } else if (parent.getType() == Token.NAME) { // on var NAME = function() { ... }; fnInfo = parent.getParent().getJSDocInfo(); } } // Compute which function parameters are optional and // which are var_args. Node args = n.getFirstChild().getNext(); for (Node arg = args.getFirstChild(); arg != null; arg = arg.getNext()) { String argName = arg.getString(); JSTypeExpression typeExpr = fnInfo == null ? null : fnInfo.getParameterType(argName); if (convention.isOptionalParameter(arg) || typeExpr != null && typeExpr.isOptionalArg()) { arg.putBooleanProp(Node.IS_OPTIONAL_PARAM, true); } if (convention.isVarArgsParameter(arg) || typeExpr

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> != null && typeExpr.isVarArgs()) { arg.putBooleanProp(Node.IS_VAR_ARGS_PARAM, true); } } break; case Token.OBJECTLIT: if (n.getType() == Token.OBJECTLIT) { for (Node key = n.getFirstChild(); key != null; key = key.getNext().getNext()) { Node value = key.getNext(); if (key.getJSDocInfo() != null && key.getNext().getType() == Token.FUNCTION) { value.setJSDocInfo(key.getJSDocInfo()); } } } break; } // TODO(johnlenz): Determine if it is possible to simply use the javadoc // everywhere rather than use IS_DISPATCHER. /* * Translate dispatcher info into the property expected node. */ if (n.getJSDocInfo() != null && n.getJSDocInfo().isJavaDispatch()) { if (n.getType() == Token.ASSIGN) { Node fnNode = n.getLastChild(); Preconditions.checkState(fnNode.getType() == Token.FUNCTION); fnNode.putBooleanProp(Node.IS_DISPATCHER, true); } } } } }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>Scope and is a // native type. if (var.input == null) { n.setJSType(varType); if (parent.getType() == Token.VAR) { if (n.getFirstChild() != null) { n.getFirstChild().setJSType(varType); } } else { Preconditions.checkState(parent.getType() == Token.FUNCTION); parent.setJSType(varType); } } else { // Always warn about duplicates if the overridden type does not // match the original type. // // If the types match, suppress the warning iff there was a @suppress // tag, or if the original declaration was a stub. if (!(allowDupe || var.getParentNode().getType() == Token.EXPR_RESULT) || !newType.equals(varType)) { compiler.report( JSError.make(sourceName, n, DUP_VAR_DECLARATION, variableName, newType.toString(), var.getInputName(), String.valueOf(var.nameNode.getLineno()), varType.toString())); } } } } /** * Expect that all properties on interfaces that this type implements are * implemented. */ void expectAllInterfacePropertiesImplemented(FunctionType type) { ObjectType instance = type.getInstanceType(); for (ObjectType implemented : type.getAllImplementedInterfaces()) { if (implemented.getImplicitPrototype() != null) { for (String prop : implemented.getImplicitPrototype().getOwnPropertyNames()) { if (!instance.hasProperty(prop)) { Node source = type.getSource(); Preconditions.checkNotNull(source); String sourceName = (String) source.getProp(Node.SOURCENAME_PROP); sourceName = sourceName == null ? "" : sourceName; compiler.report(JSError.make(sourceName, source, INTERFACE_METHOD_NOT_IMPLEMENTED, prop, implemented.toString(), instance.toString())); registerMismatch(instance, implemented); } } } } } /** * Report a type mismatch */ private void mismatch(NodeTraversal t, Node n, String msg, JSType found, JSType required) { mismatch(t.getSourceName(), n, msg, found, required); }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>/* * Copyright 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp.parsing; import com.google.common.base.Preconditions; import com.google.javascript.jscomp.mozilla.rhino.ScriptRuntime; /** * This class implements the scanner for JsDoc strings. * * It is heavily based on Rhino's TokenStream. * * */ class JsDocTokenStream { /* * For chars - because we need something out-of-range * to check. (And checking EOF by exception is annoying.) * Note distinction from EOF token type! */ private final static int EOF_CHAR = -1; JsDocTokenStream(String sourceString) { this(sourceString, 0); } JsDocTokenStream(String sourceString, int lineno) { this(sourceString, lineno, 0); } JsDocTokenStream(String sourceString, int lineno, int initCharno) { Preconditions.checkNotNull(sourceString); this.lineno = lineno; this.sourceString = sourceString; this.sourceEnd = sourceString.length(); this.sourceCursor = this.cursor = 0; this.initLineno = lineno; this.initCharno = initCharno; } /** * Tokenizes JSDoc comments. */ @SuppressWarnings("fallthrough") final JsDocToken getJsDocToken() { int c; stringBufferTop = 0; for (;;) { // eat white spaces for (;;) { charno = -1; c = getChar(); if (c == EOF_CHAR) { return

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> (lvalueToRemoveLater == n) { lvalueToRemoveLater = null; if (n.getType() == Token.ASSIGN) { Node last = n.getLastChild(); n.removeChild(last); parent.replaceChild(n, last); } else { Preconditions.checkState(n.getType() == Token.NAME); n.removeChild(n.getFirstChild()); } compiler.reportCodeChange(); } if (n.getType() == Token.CALL) { if (t.inGlobalScope()) { // If there's a function call in the global scope, // we just say it's unsafe and freeze all the defines. // // NOTE(nicksantos): We could be a lot smarter here. For example, // ReplaceOverriddenVars keeps a call graph of all functions and // which functions/variables that they reference, and tries // to statically determine which functions are "safe" and which // are not. But this would be overkill, expecially because // the intended use of defines is with config_files, where // all the defines are at the top of the bundle. for (DefineInfo info : assignableDefines.values()) { setDefineInfoNotAssignable(info, t); } assignableDefines.clear(); } } updateAssignAllowedStack(n, false); } /** * Determines whether assignment to a define should be allowed * in the subtree of the given node, and if not, records that fact. * * @param n The node whose subtree we're about to enter or exit. * @param entering True if we're entering the subtree, false otherwise. */ private void updateAssignAllowedStack(Node n, boolean entering) { switch (n.getType()) { case Token.CASE: case Token.FOR: case Token.FUNCTION: case Token.HOOK: case Token.IF: case Token.SWITCH: case Token.WHILE: if (entering) { assignAllowed.push(0); } else { assignAllowed.remove(); } break; } } /** * Determines whether assignment to a define should be allowed * at the current point of the traversal. */ private boolean isAssignAllowed() { return assignAllowed.element()

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> } break; } // FALL THROUGH case ON_FALSE: if (condition == null) { condition = NodeUtil.getConditionExpression(source); if (condition == null && source.getType() == Token.CASE) { condition = source; // conditionFlowScope is cached from previous iterations // of the loop. if (conditionFlowScope == null) { conditionFlowScope = traverse( condition.getFirstChild(), output.createChildFlowScope()); } } } if (condition != null) { if (condition.getType() == Token.AND || condition.getType() == Token.OR) { // When handling the short-circuiting binary operators, // the outcome scope on true can be different than the outcome // scope on false. // // TODO(nicksantos): The "right" way to do this is to // carry the known outcome all the way through the // recursive traversal, so that we can construct a // different flow scope based on the outcome. However, // this would require a bunch of code and a bunch of // extra computation for an edge case. This seems to be // a "good enough" approximation. // conditionOutcomes is cached from previous iterations // of the loop. if (conditionOutcomes == null) { conditionOutcomes = condition.getType() == Token.AND ? traverseAnd(condition, output.createChildFlowScope()) : traverseOr(condition, output.createChildFlowScope()); } newScope = reverseInterpreter.getPreciserScopeKnowingConditionOutcome( condition, conditionOutcomes.getOutcomeFlowScope( condition.getType(), branch == Branch.ON_TRUE), branch == Branch.ON_TRUE); } else { // conditionFlowScope is cached from previous iterations // of the loop. if (conditionFlowScope == null) { conditionFlowScope = traverse(condition, output.createChildFlowScope()); } newScope = reverseInterpreter.getPreciserScopeKnowingConditionOutcome( condition, conditionFlowScope, branch == Branch.ON_TRUE); } } break; } result.add(newScope.optimize()); } return result; } private FlowScope traverse(Node n, FlowScope scope)

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> { switch (n.getType()) { case Token.ASSIGN: scope = traverseAssign(n, scope); break; case Token.NAME: scope = traverseName(n, scope); break; case Token.GETPROP: scope = traverseGetProp(n, scope); break; case Token.AND: scope = traverseAnd(n, scope).getJoinedFlowScope() .createChildFlowScope(); break; case Token.OR: scope = traverseOr(n, scope).getJoinedFlowScope() .createChildFlowScope(); break; case Token.HOOK: scope = traverseHook(n, scope); break; case Token.OBJECTLIT: scope = traverseObjectLiteral(n, scope); break; case Token.CALL: scope = traverseCall(n, scope); break; case Token.NEW: scope = traverseNew(n, scope); break; case Token.ASSIGN_ADD: case Token.ADD: scope = traverseAdd(n, scope); break; case Token.POS: case Token.NEG: scope = traverse(n.getFirstChild(), scope); // Find types. n.setJSType(getNativeType(NUMBER_TYPE)); break; case Token.NULL: n.setJSType(getNativeType(NULL_TYPE)); break; case Token.VOID: n.setJSType(getNativeType(VOID_TYPE)); break; case Token.ARRAYLIT: scope = traverseArrayLiteral(n, scope); break; case Token.REF_SPECIAL: n.setJSType(getNativeType(UNKNOWN_TYPE)); break; case Token.REGEXP: n.setJSType(getNativeType(REGEXP_TYPE)); break; case Token.THIS: n.setJSType(scope.getTypeOfThis()); break; case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.LSH: case Token.RSH: case Token.ASSIGN_URSH: case Token.URSH: case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: case Token.ASSIGN_BITAND: case Token.ASSIGN_BITXOR: case

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> Token.ASSIGN_BITOR: case Token.ASSIGN_MUL: case Token.ASSIGN_SUB: case Token.DIV: case Token.MOD: case Token.BITAND: case Token.BITXOR: case Token.BITOR: case Token.MUL: case Token.SUB: case Token.DEC: case Token.INC: case Token.BITNOT: case Token.NUMBER: scope = traverseChildren(n, scope); n.setJSType(getNativeType(NUMBER_TYPE)); break; case Token.LP: case Token.GET_REF: scope = traverse(n.getFirstChild(), scope); n.setJSType(getJSType(n.getFirstChild())); break; case Token.COMMA: scope = traverseChildren(n, scope); n.setJSType(getJSType(n.getLastChild())); break; case Token.STRING: case Token.TYPEOF: scope = traverseChildren(n, scope); n.setJSType(getNativeType(STRING_TYPE)); break; case Token.LT: case Token.LE: case Token.GT: case Token.GE: case Token.NOT: case Token.EQ: case Token.NE: case Token.SHEQ: case Token.SHNE: case Token.INSTANCEOF: case Token.IN: case Token.TRUE: case Token.FALSE: scope = traverseChildren(n, scope); n.setJSType(getNativeType(BOOLEAN_TYPE)); break; case Token.GETELEM: scope = traverseGetElem(n, scope); break; case Token.EXPR_RESULT: scope = traverseChildren(n, scope); if (n.getFirstChild().getType() == Token.GETPROP) { ensurePropertyDeclared(n.getFirstChild()); } break; case Token.SWITCH: scope = traverse(n.getFirstChild(), scope); break; case Token.VAR: case Token.RETURN: case Token.THROW: scope = traverseChildren(n, scope); break; case Token.CATCH: scope = traverseCatch(n, scope); break; } if (n.getType() != Token.FUNCTION) {

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> JSDocInfo info = n.getJSDocInfo(); if (info != null && info.hasType()) { JSType castType = info.getType().evaluate(syntacticScope); // A stubbed type cast on a qualified name should take // effect for all subsequent accesses of that name, // so treat it the same as an assign to that name. if (n.isQualifiedName() && n.getParent().getType() == Token.EXPR_RESULT) { updateScopeForTypeChange(scope, n, n.getJSType(), castType); } n.setJSType(castType); } } return scope; } /** * Any value can be thrown, so it's really impossible to determine the type * of a CATCH param. Treat it as the UNKNOWN type. */ private FlowScope traverseCatch(Node n, FlowScope scope) { Node name = n.getFirstChild(); JSType type = getNativeType(JSTypeNative.UNKNOWN_TYPE); name.setJSType(type); redeclare(scope, name.getString(), type); return scope; } private FlowScope traverseAssign(Node n, FlowScope scope) { Node left = n.getFirstChild(); Node right = n.getLastChild(); scope = traverseChildren(n, scope); JSType leftType = left.getJSType(); JSType rightType = getJSType(right); n.setJSType(rightType); updateScopeForTypeChange(scope, left, leftType, rightType); return scope; } /** * Updates the scope according to the result of a type change, like * an assignment or a type cast. */ private void updateScopeForTypeChange( FlowScope scope, Node left, JSType leftType, JSType resultType) { Preconditions.checkNotNull(resultType); switch (left.getType()) { case Token.NAME: String varName = left.getString(); Var var = syntacticScope.getVar(varName); if (var != null && var.isLocal() && var.getScope() != syntacticScope) { assignedOuterLocalVars.put(var.getScope(), var); } // When looking at VAR initializers for declared VARs, we trust // the declared type over

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>_UNKNOWN_TYPE))) { n.setJSType(getNativeType(CHECKED_UNKNOWN_TYPE)); } } return scope; } /** * For functions with function parameters, type inference will set the type of * a function literal argument from the function parameter type. */ private void updateTypeOfParametersOnClosure(Node n, FunctionType fnType) { int i = 0; for (Node iParameter : fnType.getParameters()) { JSType iParameterType = iParameter.getJSType(); if (iParameterType instanceof FunctionType) { FunctionType iParameterFnType = (FunctionType) iParameterType; if (i + 1 >= n.getChildCount()) { // TypeCheck#visitParametersList will warn so we bail. return; } Node iArgument = n.getChildAtIndex(i + 1); JSType iArgumentType = getJSType(iArgument); if (iArgument.getType() == Token.FUNCTION && iArgumentType instanceof FunctionType && iArgumentType.getJSDocInfo() == null) { iArgument.setJSType(iParameterFnType); } } i++; } } /** * For functions with function(this: T, ...) and T as parameters, type * inference will set the type of this on a function literal argument to the * the actual type of T. */ private void updateTypeOfThisOnClosure(Node n, FunctionType fnType) { // TODO(user): Make the template logic more general. if (fnType.getTemplateTypeName() == null) { return; } int i = 0; // Find the parameter whose type is the template type. for (Node iParameter : fnType.getParameters()) { JSType iParameterType = getJSType(iParameter); iParameterType = iParameterType.restrictByNotNullOrUndefined(); if (iParameterType.isTemplateType()) { // Find the actual type of this argument. if (i + 1 >= n.getChildCount()) { // TypeCheck#visitParameterList will warn so we bail. return; } Node iArgument = n.getChildAtIndex(i + 1); JSType iArgumentType = getJSType(iArgument); if (iArgumentType != null) {

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> iArgumentType = iArgumentType.restrictByNotNullOrUndefined(); if (!(iArgumentType instanceof ObjectType)) { compiler.report( JSError.make(NodeUtil.getSourceName(iArgument), iArgument, TEMPLATE_TYPE_NOT_OBJECT_TYPE)); return; } // Find the parameter whose type is function(this: T, ...) boolean foundTemplateTypeOfThisParameter = false; int j = 0; for (Node jParameter : fnType.getParameters()) { JSType jParameterType = getJSType(jParameter); if (jParameterType instanceof FunctionType) { FunctionType jParameterFnType = (FunctionType) jParameterType; if (jParameterFnType.getTypeOfThis().equals(iParameterType)) { foundTemplateTypeOfThisParameter = true; // Find the actual type of this argument. if (j + 1 >= n.getChildCount()) { // TypeCheck#visitParameterList will warn so we bail. return; } Node jArgument = n.getChildAtIndex(j + 1); JSType jArgumentType = getJSType(jArgument); if (jArgument.getType() == Token.FUNCTION && jArgumentType instanceof FunctionType) { // If it's an anonymous function, update the type of this // using the actual type of T. FunctionType jArgumentFnType =(FunctionType) jArgumentType; if (jArgumentFnType.getTypeOfThis().isUnknownType()) { // The new type will be picked up when we traverse the inner // function. jArgument.setJSType( new FunctionType( registry, jArgumentFnType.getReferenceName(), jArgumentFnType.getSource(), jArgumentFnType.getParametersNode(), jArgumentFnType.getReturnType(), (ObjectType) iArgumentType)); } } // TODO(user): Add code to TypeCheck to check that the // types of the arguments match. } } j++; } if (!foundTemplateTypeOfThisParameter) { Node source = fnType.getSource(); compiler.report(JSError.make(NodeUtil.getSourceName(source), source, TEMPLATE_TYPE_OF_THIS_EXPECTED)); return; } } } i++; } } private FlowScope traverseNew(Node n,

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> in which to verify the right node FlowScope rightScope = reverseInterpreter. getPreciserScopeKnowingConditionOutcome( left, leftLiterals.getOutcomeFlowScope(left.getType(), condition), condition); // type the right node BooleanOutcomePair rightLiterals = traverseWithinShortCircuitingBinOp( right, rightScope.createChildFlowScope()); JSType rightType = right.getJSType(); JSType type; BooleanOutcomePair literals; if (leftType != null && rightType != null) { leftType = leftType.getRestrictedTypeGivenToBooleanOutcome(!condition); if (leftLiterals.toBooleanOutcomes == BooleanLiteralSet.get(!condition)) { // Use the restricted left type, since the right side never gets // evaluated. type = leftType; literals = leftLiterals; } else { // Use the join of the restricted left type knowing the outcome of the // ToBoolean predicate and of the right type. type = leftType.getLeastSupertype(rightType); literals = getBooleanOutcomePair(leftLiterals, rightLiterals, condition); } // Exclude the boolean type if the literal set is empty because a boolean // can never actually be returned. if (literals.booleanValues == BooleanLiteralSet.EMPTY && getNativeType(BOOLEAN_TYPE).isSubtype(type)) { // Exclusion only make sense for a union type. if (type instanceof UnionType) { type = ((UnionType) type).getRestrictedUnion( getNativeType(BOOLEAN_TYPE)); } } } else { type = null; literals = new BooleanOutcomePair( BooleanLiteralSet.BOTH, BooleanLiteralSet.BOTH, leftLiterals.getJoinedFlowScope(), rightLiterals.getJoinedFlowScope()); } n.setJSType(type); return literals; } private BooleanOutcomePair traverseWithinShortCircuitingBinOp(Node n, FlowScope scope) { switch (n.getType()) { case Token.AND: return traverseAnd(n, scope); case Token.OR: return traverseOr(n, scope); default: scope = traverse(n, scope); return newBooleanOutcomePair(n.getJSType(), scope); }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>LiteralSet toBooleanOutcomes, BooleanLiteralSet booleanValues, FlowScope leftScope, FlowScope rightScope) { this.toBooleanOutcomes = toBooleanOutcomes; this.booleanValues = booleanValues; this.leftScope = leftScope; this.rightScope = rightScope; } /** * Gets the safe estimated scope without knowing if all of the subexpressions * will be evaluated. */ FlowScope getJoinedFlowScope() { if (joinedScope == null) { if (leftScope == rightScope) { joinedScope = rightScope; } else { joinedScope = join(leftScope, rightScope); } } return joinedScope; } /** * Gets the outcome scope if we do know the outcome of the entire * expression. */ FlowScope getOutcomeFlowScope(int nodeType, boolean outcome) { if (nodeType == Token.AND && outcome || nodeType == Token.OR && !outcome) { // We know that the whole expression must have executed. return rightScope; } else { return getJoinedFlowScope(); } } } private BooleanOutcomePair newBooleanOutcomePair( JSType jsType, FlowScope flowScope) { if (jsType == null) { return new BooleanOutcomePair( BooleanLiteralSet.BOTH, BooleanLiteralSet.BOTH, flowScope, flowScope); } return new BooleanOutcomePair(jsType.getPossibleToBooleanOutcomes(), registry.getNativeType(BOOLEAN_TYPE).isSubtype(jsType) ? BooleanLiteralSet.BOTH : BooleanLiteralSet.EMPTY, flowScope, flowScope); } private void redeclare(FlowScope scope, String varName, JSType varType) { if (varType == null) { varType = getNativeType(JSTypeNative.UNKNOWN_TYPE); } if (unflowableVarNames.contains(varName)) { return; } scope.inferSlotType(varName, varType); } /** * This method gets the JSType from the Node argument and verifies that it is * present. */ private JSType getJSType(Node n) { JSType jsType = n.getJSType(); if (jsType == null) { // TODO(n

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> visit all nodes but not traverse into function * bodies. */ public abstract static class AbstractShallowCallback implements Callback { public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { // We do want to traverse the name of a named function, but we don't // want to traverse the arguments or body. return parent == null || parent.getType() != Token.FUNCTION || n == parent.getFirstChild(); } } /** * Abstract callback to visit all structure and statement nodes but doesn't * traverse into functions or expressions. */ public abstract static class AbstractShallowStatementCallback implements Callback { public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { return parent == null || NodeUtil.isControlStructure(parent) || NodeUtil.isStatementBlock(parent); } } /** * Abstract callback to visit a pruned set of nodes. * */ public abstract static class AbstractNodeTypePruningCallback implements Callback { private final Set<Integer> nodeTypes; private final boolean include; /** * Creates an abstract pruned callback. * @param nodeTypes the nodes to include in the traversal */ public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes) { this(nodeTypes, true); } /** * Creates an abstract pruned callback. * @param nodeTypes the nodes to include/exclude in the traversal * @param include whether to include or exclude the nodes in the traversal */ public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes, boolean include) { this.nodeTypes = nodeTypes; this.include = include; } public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { return include == nodeTypes.contains(n.getType()); } } /** * Creates a node traversal using the specified callback interface. */ public NodeTraversal(AbstractCompiler compiler, Callback cb) { this(compiler, cb, new SyntacticScopeCreator(compiler)); } /** * Creates a node traversal using the specified callback interface * and the scope creator. */ public NodeTraversal(AbstractCompiler compiler, Callback cb, ScopeCreator scopeCreator) { this.callback = cb;

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> if (cb instanceof ScopedCallback) { this.scopeCallback = (ScopedCallback) cb; } this.compiler = compiler; this.sourceName = ""; this.scopeCreator = scopeCreator; } private void throwUnexpectedException(Exception unexpectedException) { // If there's an unexpected exception, try to get the // line number of the code that caused it. String message = unexpectedException.getMessage(); // TODO(user): It is possible to get more information if curNode or // its parent is missing. We still have the scope stack in which it is still // very useful to find out at least which function caused the exception. if (!sourceName.isEmpty()) { message = unexpectedException.getMessage() + "\n" + formatNodeContext("Node", curNode) + (curNode == null ? "" : formatNodeContext("Parent", curNode.getParent())); } compiler.throwInternalError(message, unexpectedException); } private String formatNodeContext(String label, Node n) { if (n == null) { return " " + label + ": NULL"; } return " " + label + "(" + n.toString(false, false, false) + "): " + formatNodePosition(n); } /** * Traverses a parse tree recursively. */ public void traverse(Node root) { try { sourceName = ""; curNode = root; pushScope(root); traverseBranch(root, null); popScope(); } catch (Exception unexpectedException) { throwUnexpectedException(unexpectedException); } } public void traverseRoots(Node ... roots) { traverseRoots(Lists.newArrayList(roots)); } public void traverseRoots(List<Node> roots) { if (roots.isEmpty()) { return; } try { Node scopeRoot = roots.get(0).getParent(); Preconditions.checkState(scopeRoot != null); sourceName = ""; curNode = scopeRoot; pushScope(scopeRoot); for (Node root : roots) { Preconditions.checkState(root.getParent() == scopeRoot); traverseBranch(root, scopeRoot); } popScope(); } catch (Exception unexpectedException) { throwUnexpectedException(unexpectedException

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>); } } private static final String MISSING_SOURCE = "[source unknown]"; private String formatNodePosition(Node n) { if (n == null) { return MISSING_SOURCE + "\n"; } int lineNumber = n.getLineno(); int columnNumber = n.getCharno(); String src = compiler.getSourceLine(sourceName, lineNumber); if (src == null) { src = MISSING_SOURCE; } return sourceName + ":" + lineNumber + ":" + columnNumber + "\n" + src + "\n"; } /** * Traverses a parse tree recursively with a scope, starting with the given * root. This should only be used in the global scope. Otherwise, use * {@link #traverseAtScope}. */ void traverseWithScope(Node root, Scope s) { Preconditions.checkState(s.isGlobal()); sourceName = ""; curNode = root; pushScope(s); traverseBranch(root, null); popScope(); } /** * Traverses a parse tree recursively with a scope, starting at that scope's * root. */ void traverseAtScope(Scope s) { Node n = s.getRootNode(); if (n.getType() == Token.FUNCTION) { // We need to do some extra magic to make sure that the scope doesn't // get re-created when we dive into the function. sourceName = getSourceName(n); curNode = n; pushScope(s); Node args = n.getFirstChild().getNext(); Node body = args.getNext(); traverseBranch(args, n); traverseBranch(body, n); popScope(); } else { traverseWithScope(n, s); } } /** * Traverses an inner node recursively with a refined scope. An inner node may * be any node with a non {@code null} parent (i.e. all nodes except the * root). * * @param node the node to traverse * @param parent the node's parent, it may be not be {@code null} * @param refinedScope the refined scope of the scope currently at the top of * the scope stack or in trivial cases that very scope or {@

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>code null} */ protected void traverseInnerNode(Node node, Node parent, Scope refinedScope) { Preconditions.checkNotNull(parent); if (refinedScope != null && getScope() != refinedScope) { curNode = node; pushScope(refinedScope); traverseBranch(node, parent); popScope(); } else { traverseBranch(node, parent); } } /** * Gets the compiler. */ public Compiler getCompiler() { // TODO(nicksantos): Remove this type cast. This is just temporary // while refactoring. return (Compiler) compiler; } /** * Gets the current line number, or zero if it cannot be determined. The line * number is retrieved lazily as a running time optimization. */ public int getLineNumber() { Node cur = curNode; while (cur != null) { int line = cur.getLineno(); if (line >=0) { return line; } cur = cur.getParent(); } return 0; } /** * Gets the current input source name. * * @return A string that may be empty, but not null */ public String getSourceName() { return sourceName; } /** * Gets the current input source. */ public CompilerInput getInput() { return compiler.getInput(sourceName); } /** * Gets the current input module. */ public JSModule getModule() { CompilerInput input = getInput(); return input == null ? null : input.getModule(); } /** Returns the node currently being traversed. */ public Node getCurrentNode() { return curNode; } /** * Traverses a node recursively. */ public static void traverse( AbstractCompiler compiler, Node root, Callback cb) { NodeTraversal t = new NodeTraversal(compiler, cb); t.traverse(root); } /** * Traverses a list of node trees. */ public static void traverseRoots( AbstractCompiler compiler, List<Node> roots, Callback cb) { NodeTraversal t = new NodeTraversal(compiler, cb); t.traverseRoots(roots); } /** * Traverses a branch. */ @SuppressWarnings("

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>fallthrough") private void traverseBranch(Node n, Node parent) { int type = n.getType(); if (type == Token.SCRIPT) { sourceName = getSourceName(n); } curNode = n; if (!callback.shouldTraverse(this, n, parent)) return; switch (type) { case Token.CATCH: Preconditions.checkState(n.getChildCount() == 3); Preconditions.checkState(n.getFirstChild().getType() == Token.NAME); // the first child is the catch var and the third child // is the code block traverseBranch(n.getFirstChild(), n); traverseBranch(n.getFirstChild().getNext().getNext(), n); break; case Token.FUNCTION: traverseFunction(n, parent); break; default: for (Node child = n.getFirstChild(); child != null; ) { // child could be replaced, in which case our child node // would no longer point to the true next Node next = child.getNext(); traverseBranch(child, n); child = next; } break; } curNode = n; callback.visit(this, n, parent); } /** * Traverses a function. */ private void traverseFunction(Node n, Node parent) { Preconditions.checkState(n.getChildCount() == 3); Preconditions.checkState(n.getType() == Token.FUNCTION); final Node fnName = n.getFirstChild(); boolean anonymous = parent != null && NodeUtil.isFunctionAnonymous(n); if (!anonymous) { // Named functions are parent of the containing scope. traverseBranch(fnName, n); } curNode = n; pushScope(n); if (anonymous) { // Anonymous function names are parent of the contained scope. traverseBranch(fnName, n); } final Node args = fnName.getNext(); final Node body = args.getNext(); // Args traverseBranch(args, n); // Body Preconditions.checkState(body.getNext() == null && body.getType() == Token.BLOCK); traverseBranch(body, n); popScope(); } /** Examines the functions stack for the last instance of a function node. */

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> @SuppressWarnings("unchecked") public Node getEnclosingFunction() { if (scopes.size() + scopeRoots.size() < 2) { return null; } else { if (scopeRoots.isEmpty()) { return scopes.peek().getRootNode(); } else { return scopeRoots.peek(); } } } /** Creates a new scope (e.g. when entering a function). */ private void pushScope(Node node) { Preconditions.checkState(curNode != null); scopeRoots.push(node); cfgs.push(null); if (scopeCallback != null) { scopeCallback.enterScope(this); } } /** Creates a new scope (e.g. when entering a function). */ private void pushScope(Scope s) { Preconditions.checkState(curNode != null); scopes.push(s); cfgs.push(null); if (scopeCallback != null) { scopeCallback.enterScope(this); } } /** Pops back to the previous scope (e.g. when leaving a function). */ private void popScope() { if (scopeCallback != null) { scopeCallback.exitScope(this); } if (scopeRoots.isEmpty()) { scopes.pop(); } else { scopeRoots.pop(); } cfgs.pop(); } /** Gets the current scope. */ public Scope getScope() { Scope scope = scopes.isEmpty() ? null : scopes.peek(); if (scopeRoots.isEmpty()) { return scope; } Iterator<Node> it = scopeRoots.descendingIterator(); while (it.hasNext()) { scope = scopeCreator.createScope(it.next(), scope); scopes.push(scope); } scopeRoots.clear(); return scope; } /** Gets the control flow graph for the current JS scope. */ public ControlFlowGraph<Node> getControlFlowGraph() { if (cfgs.peek() == null) { ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false); cfa.process(null, getScopeRoot()); cfgs.pop(); cfgs.push(cfa.getCfg()); } return cfgs.peek(); } /** Returns

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>.getValue(); if (value instanceof Boolean) { map.put(name, ((Boolean) value).booleanValue() ? new Node(Token.TRUE) : new Node(Token.FALSE)); } else if (value instanceof Integer) { map.put(name, Node.newNumber(((Integer) value).intValue())); } else if (value instanceof Double) { map.put(name, Node.newNumber(((Double) value).doubleValue())); } else { Preconditions.checkState(value instanceof String); map.put(name, Node.newString((String) value)); } } return map; } /** * Sets the value of the {@code @define} variable in JS * to a boolean literal. */ public void setDefineToBooleanLiteral(String defineName, boolean value) { defineReplacements.put(defineName, new Boolean(value)); } /** * Sets the value of the {@code @define} variable in JS to a * String literal. */ public void setDefineToStringLiteral(String defineName, String value) { defineReplacements.put(defineName, value); } /** * Sets the value of the {@code @define} variable in JS to a * number literal. */ public void setDefineToNumberLiteral(String defineName, int value) { defineReplacements.put(defineName, new Integer(value)); } /** * Sets the value of the {@code @define} variable in JS to a * number literal. */ public void setDefineToDoubleLiteral(String defineName, double value) { defineReplacements.put(defineName, new Double(value)); } /** * Skip all possible passes, to make the compiler as fast as possible. */ public void skipAllCompilerPasses() { skipAllPasses = true; } /** * Whether the warnings guard in this Options object enables the given * group of warnings. */ boolean enables(DiagnosticGroup type) { return warningsGuard != null && warningsGuard.enables(type); } /** * Whether the warnings guard in this Options object disables the given * group of warnings. */ boolean disables(DiagnosticGroup type) { return warningsGuard != null && warningsGuard.disables(type

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>Annotations(compiler, assertOnChange) .process(externs, root); } public static class PropogateConstantAnnotations extends AbstractPostOrderCallback implements CompilerPass { private final AbstractCompiler compiler; private final boolean assertOnChange; public PropogateConstantAnnotations( AbstractCompiler compiler, boolean forbidChanges) { this.compiler = compiler; this.assertOnChange = forbidChanges; } @Override public void process(Node externs, Node root) { new NodeTraversal(compiler, this).traverseRoots(externs, root); } @Override public void visit(NodeTraversal t, Node n, Node parent) { // Note: Constant properties annotations are not propagated. if (n.getType() == Token.NAME) { if (n.getString().isEmpty()) { return; } JSDocInfo info = null; // Find the JSDocInfo for a top level variable. Var var = t.getScope().getVar(n.getString()); if (var != null) { info = var.getJSDocInfo(); } if ((info != null && info.isConstant()) && !n.getBooleanProp(Node.IS_CONSTANT_NAME)) { n.putBooleanProp(Node.IS_CONSTANT_NAME, true); if (assertOnChange) { String name = n.getString(); throw new IllegalStateException( "Unexpected const change.\n" + " name: "+ name + "\n" + " gramps:" + n.getParent().getParent().toStringTree()); } // Even though the AST has changed (an annotation was added), // the annotations are not compared so don't report the change. // reportCodeChange("constant annotation"); } } } } /** * Walk the AST tree and verify that constant names are used consistently. */ static class VerifyConstants extends AbstractPostOrderCallback implements CompilerPass { final private AbstractCompiler compiler; final private boolean checkUserDeclarations; VerifyConstants(AbstractCompiler compiler, boolean checkUserDeclarations) { this.compiler = compiler; this.checkUserDeclarations = checkUserDeclarations; } @Override public void process(Node externs, Node root) { Node externsAndJs = root.getParent(); Preconditions.checkState

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>(externsAndJs != null); Preconditions.checkState(externsAndJs.hasChild(externs)); NodeTraversal.traverseRoots( compiler, Lists.newArrayList(externs, root), this); } private Map<String,Boolean> constantMap = Maps.newHashMap(); @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.getType() == Token.NAME) { String name = n.getString(); if (n.getString().isEmpty()) { return; } boolean isConst = n.getBooleanProp(Node.IS_CONSTANT_NAME); if (checkUserDeclarations) { boolean expectedConst = false; if (NodeUtil.isConstantName(n) || compiler.getCodingConvention().isConstant(n.getString())) { expectedConst = true; } else { expectedConst = false; JSDocInfo info = null; Var var = t.getScope().getVar(n.getString()); if (var != null) { info = var.getJSDocInfo(); } if (info != null && info.isConstant()) { expectedConst = true; } else { expectedConst = false; } } if (expectedConst) { Preconditions.checkState(expectedConst == isConst, "The name " + name + " is not annotated as constant."); } else { Preconditions.checkState(expectedConst == isConst, "The name " + name + " should not be annotated as constant."); } } Boolean value = constantMap.get(name); if (value == null) { constantMap.put(name, isConst); } else { Preconditions.checkState(value.booleanValue() == isConst, "The name " + name + " is not consistently annotated as " + "constant."); } } } } /** * Simplify the AST: * - VAR declarations split, so they represent exactly one child * declaration. * - WHILEs are converted to FORs * - FOR loop are initializers are moved out of the FOR structure * - LABEL node of children other than LABEL, BLOCK, WHILE, FOR, or DO are * moved into a block.

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> */ static class NormalizeStatements implements Callback { private final AbstractCompiler compiler; private final boolean assertOnChange; NormalizeStatements(AbstractCompiler compiler, boolean assertOnChange) { this.compiler = compiler; this.assertOnChange = assertOnChange; } private void reportCodeChange(String changeDescription) { if (assertOnChange) { throw new IllegalStateException( "Normalize constraints violated:\n" + changeDescription); } compiler.reportCodeChange(); } @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { doStatementNormalizations(t, n, parent); return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.WHILE: if (CONVERT_WHILE_TO_FOR) { Node expr = n.getFirstChild(); n.setType(Token.FOR); Node empty = new Node(Token.EMPTY); empty.copyInformationFrom(n); n.addChildBefore(empty, expr); n.addChildAfter(empty.cloneNode(), expr); reportCodeChange("WHILE node"); } break; case Token.FUNCTION: normalizeFunctionDeclaration(n); break; } } /** * Rewrite named unhoisted functions declarations to a known * consistent behavior so we don't to different logic paths for the same * code. From: * function f() {} * to: * var f = function () {}; */ private void normalizeFunctionDeclaration(Node n) { Preconditions.checkState(n.getType() == Token.FUNCTION); if (!NodeUtil.isFunctionAnonymous(n) && !NodeUtil.isHoistedFunctionDeclaration(n)) { rewriteFunctionDeclaration(n); } } /** * Rewrite the function declaration from: * function x() {} * FUNCTION * NAME * LP * BLOCK * to: * var x = function() {}; * VAR * NAME * FUNCTION * NAME (w/ empty string) * LP * BLOCK */ private void rewriteFunctionDeclaration(Node n) { // Prepare a spot for the function. Node oldNameNode = n.

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>getFirstChild(); Node fnNameNode = oldNameNode.cloneNode(); Node var = new Node(Token.VAR, fnNameNode, n.getLineno(), n.getCharno()); var.copyInformationFrom(n); // Prepare the function oldNameNode.setString(""); // Move the function Node parent = n.getParent(); parent.replaceChild(n, var); fnNameNode.addChildToFront(n); reportCodeChange("Function declaration"); } /** * Do normalizations that introduce new siblings or parents. */ private void doStatementNormalizations( NodeTraversal t, Node n, Node parent) { if (n.getType() == Token.LABEL) { normalizeLabels(n); } // Only inspect the children of SCRIPTs, BLOCKs and LABELs, as all these // are the only legal place for VARs and FOR statements. if (NodeUtil.isStatementBlock(n) || n.getType() == Token.LABEL) { extractForInitializer(n, null, null); } // Only inspect the children of SCRIPTs, BLOCKs, as all these // are the only legal place for VARs. if (NodeUtil.isStatementBlock(n)) { splitVarDeclarations(n); } if (n.getType() == Token.FUNCTION) { moveNamedFunctions(n.getLastChild()); } } // TODO(johnlenz): Move this to NodeTypeNormalizer once the unit tests are // fixed. /** * Limit the number of special cases where LABELs need to be handled. Only * BLOCK and loops are allowed to be labeled. Loop labels must remain in * place as the named continues are not allowed for labeled blocks. */ private void normalizeLabels(Node n) { Preconditions.checkArgument(n.getType() == Token.LABEL); Node last = n.getLastChild(); switch (last.getType()) { case Token.LABEL: case Token.BLOCK: case Token.FOR: case Token.WHILE: case Token.DO: return; default: Node block = new Node(Token.BLOCK); block.copyInformationFrom(last); n.replaceChild(last, block); block.addChildToFront(last); reportCode

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>hasChildren()) { throw new IllegalStateException("Empty VAR node."); } while (c.getFirstChild() != c.getLastChild()) { Node name = c.getFirstChild(); c.removeChild(name); Node newVar = new Node( Token.VAR, name, n.getLineno(), n.getCharno()); n.addChildBefore(newVar, c); reportCodeChange("VAR with multiple children"); } } } } /** * Move all the functions that are valid at the execution of the first * statement of the function to the beginning of the function definition. */ private void moveNamedFunctions(Node functionBody) { Preconditions.checkState( functionBody.getParent().getType() == Token.FUNCTION); Node previous = null; Node current = functionBody.getFirstChild(); // Skip any declarations at the beginning of the function body, they // are already in the right place. while (current != null && NodeUtil.isFunctionDeclaration(current)) { previous = current; current = current.getNext(); } // Find any remaining declarations and move them. Node insertAfter = previous; while (current != null) { // Save off the next node as the current node maybe removed. Node next = current.getNext(); if (NodeUtil.isFunctionDeclaration(current)) { // Remove the declaration from the body. Preconditions.checkNotNull(previous); functionBody.removeChildAfter(previous); // Readd the function at the top of the function body (after any // previous declarations). insertAfter = addToFront(functionBody, current, insertAfter); reportCodeChange("Move function declaration not at top of function"); } else { // Update the previous only if the current node hasn't been moved. previous = current; } current = next; } } /** * @param after The child node to insert the newChild after, or null if * newChild should be added to the front of parent's child list. * @return The inserted child node. */ private Node addToFront(Node parent, Node newChild, Node after) { if (after == null) { parent.addChildToFront(newChild); } else { parent.addChildAfter(newChild, after); } return newChild;

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> } } /** * Remove duplicate VAR declarations. */ private void removeDuplicateDeclarations(Node root) { Callback tickler = new ScopeTicklingCallback(); ScopeCreator scopeCreator = new SyntacticScopeCreator( compiler, new DuplicateDeclarationHandler()); NodeTraversal t = new NodeTraversal(compiler, tickler, scopeCreator); t.traverse(root); } /** * ScopeCreator duplicate declaration handler. */ private final class DuplicateDeclarationHandler implements SyntacticScopeCreator.RedeclarationHandler { /** * Remove duplicate VAR declarations encountered discovered during * scope creation. */ @Override public void onRedeclaration( Scope s, String name, Node n, Node parent, Node gramps, Node nodeWithLineNumber) { Preconditions.checkState(n.getType() == Token.NAME); Var v = s.getVar(name); // If name is "arguments", Var maybe null. Preconditions.checkState( v == null || v.getParentNode().getType() != Token.CATCH); if (v != null && parent.getType() == Token.FUNCTION) { if (v.getParentNode().getType() == Token.VAR) { s.undeclare(v); s.declare(name, n, n.getJSType(), v.input); replaceVarWithAssignment(v.getNameNode(), v.getParentNode(), v.getParentNode().getParent()); } } else if (parent.getType() == Token.VAR) { Preconditions.checkState(parent.hasOneChild()); replaceVarWithAssignment(n, parent, gramps); } } /** * Remove the parent VAR. There are three cases that need to be handled: * 1) "var a = b;" which is replaced with "a = b" * 2) "label:var a;" which is replaced with "label:;". Ideally, the * label itself would be removed but that is not possible in the * context in which "onRedeclaration" is called. * 3) "for (var a in b) ..." which is replaced with "for (a in b)..." * Cases we don't need to handle are VARs with multiple children, * which have already been split into separate declarations, so there *

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> is no need to handle that here, and "for (var a;;);", which has * been moved out of the loop. * The result of this is that in each case the parent node is replaced * which is generally dangerous in a traversal but is fine here with * the scope creator, as the next node of interest is the parent's * next sibling. */ private void replaceVarWithAssignment(Node n, Node parent, Node gramps) { if (n.hasChildren()) { // The * is being initialize, preserve the new value. parent.removeChild(n); // Convert "var name = value" to "name = value" Node value = n.getFirstChild(); n.removeChild(value); Node replacement = new Node(Token.ASSIGN, n, value); replacement.copyInformationFrom(parent); gramps.replaceChild(parent, NodeUtil.newExpr(replacement)); } else { // It is an empty reference remove it. if (NodeUtil.isStatementBlock(gramps)) { gramps.removeChild(parent); } else if (gramps.getType() == Token.FOR) { // This is the "for (var a in b)..." case. We don't need to worry // about initializers in "for (var a;;)..." as those are moved out // as part of the other normalizations. parent.removeChild(n); gramps.replaceChild(parent, n); } else { Preconditions.checkState(gramps.getType() == Token.LABEL); // We should never get here. LABELs with a single VAR statement should // already have been normalized to have a BLOCK. throw new IllegalStateException("Unexpected LABEL"); } } reportCodeChange("Duplicate VAR declaration"); } } /** * A simple class that causes scope to be created. */ private final class ScopeTicklingCallback implements NodeTraversal.ScopedCallback { @Override public void enterScope(NodeTraversal t) { // Cause the scope to be created, which will cause duplicate // to be found. t.getScope(); } @Override public void exitScope(NodeTraversal t) { // Nothing to do. } @Override public boolean

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> the externs parse tree. * @param jsRoot The root of the input parse tree to be checked. */ public void process(Node externsRoot, Node jsRoot) { Node externsAndJs = jsRoot.getParent(); Preconditions.checkState(externsAndJs != null); Preconditions.checkState( externsRoot == null || externsAndJs.hasChild(externsRoot)); inferTypes(externsAndJs); } /** Entry point for type inference when running over part of the tree. */ void inferTypes(Node node) { NodeTraversal inferTypes = new NodeTraversal( compiler, new TypeInferringCallback(), scopeCreator); inferTypes.traverseWithScope(node, topScope); } private Collection<Var> getUnflowableVars(Scope scope) { List<Var> vars = Lists.newArrayList(); for (Scope current = scope; current.isLocal(); current = current.getParent()) { vars.addAll(escapedLocalVars.get(current)); } return vars; } void inferTypes(NodeTraversal t, Node n, Scope scope) { TypeInference typeInference = new TypeInference( compiler, computeCfg(n), reverseInterpreter, scope, getUnflowableVars(scope)); try { typeInference.analyze(); escapedLocalVars.putAll(typeInference.getAssignedOuterLocalVars()); // Resolve any new type names found during the inference. compiler.getTypeRegistry().resolveTypesInScope(scope); } catch (DataFlowAnalysis.MaxIterationsExceededException e) { compiler.report(JSError.make(t, n, DATAFLOW_ERROR)); } } private class TypeInferringCallback implements ScopedCallback { public void enterScope(NodeTraversal t) { Scope scope = t.getScope(); Node node = t.getCurrentNode(); if (scope.isGlobal()) { inferTypes(t, node, scope); } } public void exitScope(NodeTraversal t) { Scope scope = t.getScope(); Node node = t.getCurrentNode(); if (scope.isLocal()) { inferTypes(t, node, scope); } } public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { return true; }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>(); // Save the tree for later comparison. Node rootClone = root.cloneTree(); Node externsRootClone = rootClone.getFirstChild(); Node mainRootClone = rootClone.getLastChild(); int numRepetitions = getNumRepetitions(); ErrorManager[] errorManagers = new ErrorManager[numRepetitions]; int aggregateWarningCount = 0; List<JSError> aggregateWarnings = Lists.newArrayList(); boolean hasCodeChanged = false; assertFalse("Code should not change before processing", recentChange.hasCodeChanged()); for (int i = 0; i < numRepetitions; ++i) { if (compiler.getErrorCount() == 0) { errorManagers[i] = new BlackHoleErrorManager(compiler); // Only run the type checking pass once, if asked. // Running it twice can cause unpredictable behavior because duplicate // objects for the same type are created, and the type system // uses reference equality to compare many types. if (typeCheckEnabled && i == 0) { TypeCheck check = createTypeCheck(compiler, typeCheckLevel); check.processForTesting(externsRoot, mainRoot); } // Only run the normalize pass once, if asked. if (normalizeEnabled && i == 0) { Normalize normalize = new Normalize(compiler, false); normalize.process(externsRoot, mainRoot); compiler.setNormalized(); } if (markNoSideEffects && i == 0) { MarkNoSideEffectCalls mark = new MarkNoSideEffectCalls(compiler); mark.process(externsRoot, mainRoot); } recentChange.reset(); getProcessor(compiler).process(externsRoot, mainRoot); if (checkLineNumbers) { (new LineNumberCheck(compiler)).process(externsRoot, mainRoot); } hasCodeChanged = hasCodeChanged || recentChange.hasCodeChanged(); aggregateWarningCount += errorManagers[i].getWarningCount(); aggregateWarnings.addAll(Lists.newArrayList(compiler.getWarnings())); if (normalizeEnabled) { boolean verifyDeclaredConstants = true; new Normalize.VerifyConstants(compiler, verifyDeclaredConstants) .process(externsRoot, mainRoot); } } } if (error == null) {

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> not be modified. private boolean frozen = false; // The last slot defined in this flow instruction, and the head of the // linked list of slots. private LinkedFlowSlot lastSlot; private LinkedFlowScope(FlatFlowScopeCache cache, LinkedFlowScope directParent) { this.cache = cache; if (directParent == null) { this.lastSlot = null; this.depth = 0; this.parent = cache.linkedEquivalent; } else { this.lastSlot = directParent.lastSlot; this.depth = directParent.depth + 1; this.parent = directParent; } } LinkedFlowScope(FlatFlowScopeCache cache) { this(cache, null); } LinkedFlowScope(LinkedFlowScope directParent) { this(directParent.cache, directParent); } /** Gets the function scope for this flow scope. */ private Scope getFunctionScope() { return cache.functionScope; } /** Whether this flows from a bottom scope. */ private boolean flowsFromBottom() { return getFunctionScope().isBottom(); } /** * Creates an entry lattice for the flow. */ public static LinkedFlowScope createEntryLattice(Scope scope) { return new LinkedFlowScope(new FlatFlowScopeCache(scope)); } @Override public void inferSlotType(String symbol, JSType type) { Preconditions.checkState(!frozen); lastSlot = new LinkedFlowSlot(symbol, type, lastSlot); depth++; cache.dirtySymbols.add(symbol); } @Override public void inferQualifiedSlot(String symbol, JSType bottomType, JSType inferredType) { Scope functionScope = getFunctionScope(); if (functionScope.isLocal()) { if (functionScope.getVar(symbol) == null && !functionScope.isBottom()) { // When we enter a local scope, many qualified names are // already defined even if they haven't been declared in the Scope // object. If the name has not yet been defined in this scope, we // need to define it now before we refine it. functionScope.declare(symbol, null, bottomType, null); } inferSlotType(symbol, inferredType); } } @Override

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> Join the two types. // 4) The type is declared in functionScope and joinedScopeB, but // not in joinedScopeA. Join the two types. // 5) The type is declared in joinedScopeA and joinedScopeB. Join // the two types. Set<String> symbolNames = Sets.newHashSet(symbols.keySet()); symbolNames.addAll(slotsB.keySet()); for (String name : symbolNames) { StaticSlot<JSType> slotA = slotsA.get(name); StaticSlot<JSType> slotB = slotsB.get(name); JSType joinedType = null; if (slotB == null || slotB.getType() == null) { StaticSlot<JSType> fnSlot = joinedScopeB.getFunctionScope().getSlot(name); JSType fnSlotType = fnSlot == null ? null : fnSlot.getType(); if (fnSlotType == null) { // Case #1 -- already inserted. } else { // Case #3 joinedType = slotA.getType().getLeastSupertype(fnSlotType); } } else if (slotA == null || slotA.getType() == null) { StaticSlot<JSType> fnSlot = joinedScopeA.getFunctionScope().getSlot(name); JSType fnSlotType = fnSlot == null ? null : fnSlot.getType(); if (fnSlotType == null) { // Case #2 symbols.put(name, slotB); } else { // Case #4 joinedType = slotB.getType().getLeastSupertype(fnSlotType); } } else { // Case #5 joinedType = slotA.getType().getLeastSupertype(slotB.getType()); } if (joinedType != null) { symbols.put(name, new SimpleSlot(name, joinedType, true)); } } } /** * Get the slot for the given symbol. */ public StaticSlot<JSType> getSlot(String name) { if (symbols.containsKey(name)) { return symbols.get(name); } else { return functionScope.getSlot(name); } } } }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>IN_EXTERNS_ERROR = DiagnosticType.warning( "JSC_NAME_REFERENCE_IN_EXTERNS", "accessing name {0} in externs has no effect"); static final DiagnosticType INVALID_FUNCTION_DECL = DiagnosticType.error("JSC_INVALID_FUNCTION_DECL", "Syntax error: function declaration must have a name"); private CompilerInput synthesizedExternsInput = null; private Node synthesizedExternsRoot = null; private final AbstractCompiler compiler; // Whether this is the post-processing sanity check. private final boolean sanityCheck; VarCheck(AbstractCompiler compiler) { this(compiler, false); } VarCheck(AbstractCompiler compiler, boolean sanityCheck) { this.compiler = compiler; this.sanityCheck = sanityCheck; } /** {@inheritDoc} */ public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, externs, new NameRefInExternsCheck()); NodeTraversal.traverseRoots( compiler, Lists.newArrayList(externs, root), this); } /** {@inheritDoc} */ public void visit(NodeTraversal t, Node n, Node parent) { if (n.getType() != Token.NAME) { return; } if (NodeUtil.isLabelName(n)) { return; } String varName = n.getString(); // Only a function can have an empty name. if (varName.isEmpty()) { Preconditions.checkState(NodeUtil.isFunction(parent)); // A function declaration with an empty name passes Rhino, // but is supposed to be a syntax error according to the spec. if (!NodeUtil.isAnonymousFunction(parent)) { t.report(n, INVALID_FUNCTION_DECL); } return; } // Check that the var has been declared. Scope scope = t.getScope(); Scope.Var var = scope.getVar(varName); if (var == null) { if (NodeUtil.isAnonymousFunction(parent)) { // e.g. [ function foo() {} ], it's okay if "foo" isn't defined in the // current scope. } else { t.report(n, UNDEFINED_VAR_ERROR, varName); if

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>NodeTraversal t, Node n, Node parent) { if (n.getType() == Token.NAME) { switch (parent.getType()) { case Token.VAR: case Token.FUNCTION: case Token.GETPROP: case Token.LP: // These are okay. break; default: t.report(n, NAME_REFERENCE_IN_EXTERNS_ERROR, n.getString()); break; } } } } /** Lazily create a "new" externs input for undeclared variables. */ private CompilerInput getSynthesizedExternsInput() { if (synthesizedExternsInput == null) { synthesizedExternsInput = compiler.newExternInput("{SyntheticVarsDeclar}"); } return synthesizedExternsInput; } /** Lazily create a "new" externs root for undeclared variables. */ private Node getSynthesizedExternsRoot() { if (synthesizedExternsRoot == null) { CompilerInput synthesizedExterns = getSynthesizedExternsInput(); synthesizedExternsRoot = synthesizedExterns.getAstRoot(compiler); } return synthesizedExternsRoot; } }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>.traverse(compiler, root, functionListExtractor); AnonymousFunctionNamer namer = new AnonymousFunctionNamer(functionMap); AnonymousFunctionNamingCallback namingCallback = new AnonymousFunctionNamingCallback(namer); NodeTraversal.traverse(compiler, root, namingCallback); } public Iterable<Node> getFunctionNodeList() { return functionMap.keySet(); } public int getFunctionId(Node f) { FunctionRecord record = functionMap.get(f); if (record != null) { return record.id; } else { return -1; } } public String getFunctionName(Node f) { FunctionRecord record = functionMap.get(f); if (record == null) { // Function node was added during compilation and has no name. return null; } String str = record.name; if (str.isEmpty()) { str = "<anonymous>"; } Node parent = record.parent; if (parent != null) { str = getFunctionName(parent) + "::" + str; } // this.foo -> foo str = str.replaceAll("::this\\.", "."); // foo.prototype.bar -> foo.bar // AnonymousFunctionNamingCallback already replaces ".prototype." // with "..", just remove the extra dot. str = str.replaceAll("\\.\\.", "."); // remove toplevel anonymous blocks, if they exists. str = str.replaceFirst("^(<anonymous>::)*", ""); return str; } private static class FunctionRecord implements Serializable { private static final long serialVersionUID = 1L; public final int id; public final Node parent; public String name; FunctionRecord(int id, Node parent, String name) { this.id = id; this.parent = parent; this.name = name; } } private static class FunctionListExtractor extends AbstractPostOrderCallback { private final Map<Node, FunctionRecord> functionMap; private int nextId = 0; FunctionListExtractor(Map<Node, FunctionRecord> functionMap) { this.functionMap = functionMap; } public void visit(NodeTraversal t, Node n, Node parent) { if (n.getType() == Token.FUNCTION) { Node functionNameNode =

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> String getName() { return name; } /** Adds a source file input to this module. */ public void add(JSSourceFile file) { add(new CompilerInput(file)); } /** Adds a source file input to this module. */ public void addFirst(JSSourceFile file) { addFirst(new CompilerInput(file)); } /** Adds a source code input to this module. */ public void add(CompilerInput input) { inputs.add(input); input.setModule(this); } /** Adds a source code input to this module. */ public void addFirst(CompilerInput input) { inputs.add(0, input); input.setModule(this); } /** Adds a source code input to this module directly after other. */ public void addAfter(CompilerInput input, CompilerInput other) { Preconditions.checkState(inputs.contains(other)); inputs.add(inputs.indexOf(other), input); input.setModule(this); } /** Adds a dependency on another module. */ public void addDependency(JSModule dep) { Preconditions.checkState(dep != this); deps.add(dep); } /** Removes all of the inputs from this module. */ public void removeAll() { for (CompilerInput input : inputs) { input.setModule(null); } inputs.clear(); } /** * Gets the list of modules that this module depends on. * * @return A list that may be empty but not null */ public List<JSModule> getDependencies() { return deps; } /** * Returns the transitive closure of dependencies starting from the * dependencies of this module. */ public Set<JSModule> getAllDependencies() { Set<JSModule> allDeps = Sets.newHashSet(deps); List<JSModule> workList = Lists.newArrayList(deps); while (workList.size() > 0) { JSModule module = workList.remove(workList.size() - 1); for (JSModule dep : module.getDependencies()) { if (allDeps.add(dep)) { workList.add(dep); } } } return allDeps; } /** Returns this

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> dep = provides.get(req); if (dep != null) { deps.put(input, dep); } } } // Sort the JSModule in this order. List<CompilerInput> sortedList = topologicalStableSort( inputs, deps); inputs.clear(); inputs.addAll(sortedList); } /** * Returns the given collection of modules in topological order. * * Note that this will return the modules in the same order if they are * already sorted, and in general, will only change the order as necessary to * satisfy the ordering dependencies. This can be important for cases where * the modules do not properly specify all dependencies. */ public static JSModule[] sortJsModules(Collection<JSModule> modules) { final Multimap<JSModule, JSModule> deps = HashMultimap.create(); for (JSModule module : modules) { for (JSModule dep : module.getDependencies()) { deps.put(module, dep); } } // Sort the JSModule in this order. List<JSModule> sortedList = topologicalStableSort( Lists.newArrayList(modules), deps); return sortedList.toArray(new JSModule[sortedList.size()]); } private static <T> List<T> topologicalStableSort( List<T> items, Multimap<T, T> deps) { final Map<T, Integer> originalIndex = Maps.newHashMap(); for (int i = 0; i < items.size(); i++) { originalIndex.put(items.get(i), i); } PriorityQueue<T> inDegreeZero = new PriorityQueue<T>(items.size(), new Comparator<T>() { @Override public int compare(T a, T b) { return originalIndex.get(a).intValue() - originalIndex.get(b).intValue(); } }); List<T> result = Lists.newArrayList(); Multiset<T> inDegree = HashMultiset.create(); Multimap<T, T> reverseDeps = ArrayListMultimap.create(); Multimaps.invertFrom(deps, reverseDeps); // First, add all the inputs with in-degree 0. for (T item : items) { Collection<T

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>stype.JSTypeRegistry; import com.google.javascript.rhino.jstype.ObjectType; import com.google.javascript.rhino.jstype.StaticSlot; import com.google.javascript.rhino.jstype.UnionType; import com.google.javascript.rhino.jstype.Visitor; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; /** * Chainable reverse abstract interpreter providing basic functionality. * * */ abstract class ChainableReverseAbstractInterpreter implements ReverseAbstractInterpreter { protected final CodingConvention convention; final JSTypeRegistry typeRegistry; private ChainableReverseAbstractInterpreter firstLink; private ChainableReverseAbstractInterpreter nextLink; /** * Constructs an interpreter, which is the only link in a chain. Interpreters * can be appended using {@link #append}. */ ChainableReverseAbstractInterpreter(CodingConvention convention, JSTypeRegistry typeRegistry) { Preconditions.checkNotNull(convention); this.convention = convention; this.typeRegistry = typeRegistry; firstLink = this; nextLink = null; } /** * Appends a link to {@code this}, returning the updated last link. * <p> * The pattern {@code new X().append(new Y())...append(new Z())} forms a * chain starting with X, then Y, then ... Z. * @param lastLink a chainable interpreter, with no next link * @return the updated last link */ ChainableReverseAbstractInterpreter append( ChainableReverseAbstractInterpreter lastLink) { Preconditions.checkArgument(lastLink.nextLink == null); this.nextLink = lastLink; lastLink.firstLink = this.firstLink; return lastLink; } /** * Gets the first link of this chain. */ ChainableReverseAbstractInterpreter getFirst() { return firstLink; } /** * Calculates the preciser scope starting with the first link. */ protected FlowScope firstPreciserScopeKnowingConditionOutcome(Node condition, FlowScope blindScope, boolean outcome) { return firstLink.getPreciserScopeKnowingConditionOutcome( condition, blindScope, outcome); } /** * Delegates the calculation

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> qualifiedName = node.getQualifiedName(); Preconditions.checkNotNull(qualifiedName); JSType origType = node.getJSType(); origType = origType == null ? getNativeType(UNKNOWN_TYPE) : origType; scope.inferQualifiedSlot(qualifiedName, origType, type); break; default: throw new IllegalArgumentException("Node cannot be refined. \n" + node.toStringTree()); } } /** * @see #getRestrictedWithoutUndefined(JSType) */ private final Visitor<JSType> restrictUndefinedVisitor = new Visitor<JSType>() { public JSType caseEnumElementType(EnumElementType enumElementType) { JSType type = enumElementType.getPrimitiveType().visit(this); if (type != null && enumElementType.getPrimitiveType().equals(type)) { return enumElementType; } else { return type; } } public JSType caseAllType() { return typeRegistry.createUnionType(OBJECT_TYPE, NUMBER_TYPE, STRING_TYPE, BOOLEAN_TYPE, NULL_TYPE); } public JSType caseNoObjectType() { return getNativeType(NO_OBJECT_TYPE); } public JSType caseNoType() { return getNativeType(NO_TYPE); } public JSType caseBooleanType() { return getNativeType(BOOLEAN_TYPE); } public JSType caseFunctionType(FunctionType type) { return type; } public JSType caseNullType() { return getNativeType(NULL_TYPE); } public JSType caseNumberType() { return getNativeType(NUMBER_TYPE); } public JSType caseObjectType(ObjectType type) { return type; } public JSType caseStringType() { return getNativeType(STRING_TYPE); } public JSType caseUnionType(UnionType type) { return type.getRestrictedUnion(getNativeType(VOID_TYPE)); } public JSType caseUnknownType() { return getNativeType(UNKNOWN_TYPE); } public JSType caseVoidType() { return null; } }; /** * @see #getRestrictedWithoutNull(JSType) */ private final Visitor<JSType> restrictNullVisitor = new Visitor<JSType>() { public JS

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>JSDocInfo inferJSDocInfo = null; // These fields are used to calculate the percentage of expressions typed. private int typedCount = 0; private int nullCount = 0; private int unknownCount = 0; private boolean inExterns; public TypeCheck(AbstractCompiler compiler, ReverseAbstractInterpreter reverseInterpreter, JSTypeRegistry typeRegistry, Scope topScope, ScopeCreator scopeCreator, CheckLevel reportMissingOverride, CheckLevel reportUnknownTypes) { this.compiler = compiler; this.validator = compiler.getTypeValidator(); this.reverseInterpreter = reverseInterpreter; this.typeRegistry = typeRegistry; this.topScope = topScope; this.scopeCreator = scopeCreator; this.reportMissingOverride = reportMissingOverride; this.reportUnknownTypes = reportUnknownTypes; this.inferJSDocInfo = new InferJSDocInfo(compiler); } public TypeCheck(AbstractCompiler compiler, ReverseAbstractInterpreter reverseInterpreter, JSTypeRegistry typeRegistry, CheckLevel reportMissingOverride, CheckLevel reportUnknownTypes) { this(compiler, reverseInterpreter, typeRegistry, null, null, reportMissingOverride, reportUnknownTypes); } TypeCheck(AbstractCompiler compiler, ReverseAbstractInterpreter reverseInterpreter, JSTypeRegistry typeRegistry) { this(compiler, reverseInterpreter, typeRegistry, null, null, CheckLevel.WARNING, CheckLevel.OFF); } /** Turn on the missing property check. Returns this for easy chaining. */ TypeCheck reportMissingProperties(boolean report) { reportMissingProperties = report; return this; } /** * Main entry point for this phase of processing. This follows the pattern for * JSCompiler phases. * * @param externsRoot The root of the externs parse tree. * @param jsRoot The root of the input parse tree to be checked. */ public void process(Node externsRoot, Node jsRoot) { Preconditions.checkNotNull(scopeCreator); Preconditions.checkNotNull(topScope); Node externsAndJs = jsRoot.getParent(); Preconditions.checkState(externsAndJs != null); Preconditions.checkState( externsRoot == null || externsAndJs.hasChild(externsRoot)); if (externsRoot

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> != null) { check(externsRoot, true); } check(jsRoot, false); potentialChecks.flush(); } /** Main entry point of this phase for testing code. */ public Scope processForTesting(Node externsRoot, Node jsRoot) { Preconditions.checkState(scopeCreator == null); Preconditions.checkState(topScope == null); Preconditions.checkState(jsRoot.getParent() != null); Node externsAndJsRoot = jsRoot.getParent(); scopeCreator = new MemoizedScopeCreator(new TypedScopeCreator(compiler)); topScope = scopeCreator.createScope(externsAndJsRoot, null); TypeInferencePass inference = new TypeInferencePass(compiler, reverseInterpreter, topScope, scopeCreator); inference.process(externsRoot, jsRoot); process(externsRoot, jsRoot); return topScope; } public void check(Node node, boolean externs) { Preconditions.checkNotNull(node); NodeTraversal t = new NodeTraversal(compiler, this, scopeCreator); inExterns = externs; t.traverseWithScope(node, topScope); if (externs) { inferJSDocInfo.process(node, null); } else { inferJSDocInfo.process(null, node); } } public boolean shouldTraverse( NodeTraversal t, Node n, Node parent) { JSDocInfo info; switch (n.getType()) { case Token.SCRIPT: case Token.VAR: // @notypecheck info = n.getJSDocInfo(); if (info != null && info.isNoTypeCheck()) { return false; } break; case Token.FUNCTION: // @notypecheck info = n.getJSDocInfo(); info = (info == null) ? parent.getJSDocInfo() : info; if (info != null && info.isNoTypeCheck()) { return false; } // normal type checking final TypeCheck outerThis = this; final Scope outerScope = t.getScope(); final FunctionType functionType = (FunctionType) n.getJSType(); final String functionPrivateName = n.getFirstChild().getString(); if (functionPrivateName != null

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> && functionPrivateName.length() > 0 && outerScope.isDeclared(functionPrivateName, false) && // Ideally, we would want to check whether the type in the scope // differs from the type being defined, but then the extern // redeclarations of built-in types generates spurious warnings. !(outerScope.getVar( functionPrivateName).getType() instanceof FunctionType)) { t.report(n, FUNCTION_MASKS_VARIABLE, functionPrivateName); } // TODO(user): Only traverse the function's body. The function's // name and arguments are traversed by the scope creator, and ideally // should not be traversed by the type checker. break; } return true; } /** * This is the meat of the type checking. It is basically one big switch, * with each case representing one type of parse tree node. The individual * cases are usually pretty straightforward. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. * @param parent The parent of the node n. */ public void visit(NodeTraversal t, Node n, Node parent) { JSType childType; JSType leftType, rightType; Node left, right; // To be explicitly set to false if the node is not typeable. boolean typeable = true; switch (n.getType()) { case Token.NAME: typeable = visitName(t, n, parent); break; case Token.LP: // If this is under a FUNCTION node, it is a parameter list and can be // ignored here. if (parent.getType() != Token.FUNCTION) { ensureTyped(t, n, getJSType(n.getFirstChild())); } else { typeable = false; } break; case Token.COMMA: ensureTyped(t, n, getJSType(n.getLastChild())); break; case Token.TRUE: case Token.FALSE: ensureTyped(t, n, BOOLEAN_TYPE); break; case Token.THIS: ensureTyped(t, n, t.getScope().getTypeOfThis());

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>_RSH: case Token.ASSIGN_URSH: case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: case Token.ASSIGN_BITOR: case Token.ASSIGN_BITXOR: case Token.ASSIGN_BITAND: case Token.ASSIGN_SUB: case Token.ASSIGN_ADD: case Token.ASSIGN_MUL: case Token.LSH: case Token.RSH: case Token.URSH: case Token.DIV: case Token.MOD: case Token.BITOR: case Token.BITXOR: case Token.BITAND: case Token.SUB: case Token.ADD: case Token.MUL: visitBinaryOperator(n.getType(), t, n); break; case Token.DELPROP: if (!isReference(n.getFirstChild())) { t.report(n, BAD_DELETE); } ensureTyped(t, n, BOOLEAN_TYPE); break; case Token.CASE: JSType switchType = getJSType(parent.getFirstChild()); JSType caseType = getJSType(n.getFirstChild()); validator.expectSwitchMatchesCase(t, n, switchType, caseType); typeable = false; break; case Token.WITH: { Node child = n.getFirstChild(); childType = getJSType(child); validator.expectObject( t, child, childType, "with requires an object"); typeable = false; break; } case Token.FUNCTION: visitFunction(t, n); break; // These nodes have no interesting type behavior. case Token.LABEL: case Token.SWITCH: case Token.BREAK: case Token.CATCH: case Token.TRY: case Token.SCRIPT: case Token.EXPR_RESULT: case Token.BLOCK: case Token.EMPTY: case Token.DEFAULT: case Token.CONTINUE: case Token.DEBUGGER: case Token.THROW: typeable = false; break; // These nodes require data flow analysis. case Token.DO: case Token.FOR: case Token.IF: case Token.WHILE: typeable = false; break; // These nodes are typed during the type inference. case Token

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>.AND: case Token.HOOK: case Token.OBJECTLIT: case Token.OR: if (n.getJSType() != null) { // If we didn't run type inference. ensureTyped(t, n); } else { // If this is an enum, then give that type to the objectlit as well. if ((n.getType() == Token.OBJECTLIT) && (parent.getJSType() instanceof EnumType)) { ensureTyped(t, n, parent.getJSType()); } else { ensureTyped(t, n); } } break; default: t.report(n, UNEXPECTED_TOKEN, Token.name(n.getType())); ensureTyped(t, n); break; } // Don't count externs since the user's code may not even use that part. typeable = typeable && !inExterns; if (typeable) { doPercentTypedAccounting(t, n); } } /** * Counts the given node in the typed statistics. * @param n a node that should be typed */ private void doPercentTypedAccounting(NodeTraversal t, Node n) { JSType type = n.getJSType(); if (type == null) { nullCount++; } else if (type.isUnknownType()) { if (reportUnknownTypes.isOn()) { String unresolvedReference = getUnresolvedReference(type); if (unresolvedReference != null) { compiler.report(JSError.make(t, n, reportUnknownTypes, UNRESOLVED_TYPE, unresolvedReference)); } else { compiler.report(JSError.make(t, n, reportUnknownTypes, UNKNOWN_EXPR_TYPE)); } } unknownCount++; } else { typedCount++; } } /** * Looks through the type to see if it contains an unresolved reference. This * is often the reason that a type is unresolved, and it can occur because of * a simple misspelling of a type name. */ private String getUnresolvedReference(JSType type) { if (type.isNamedType()) { NamedType namedType = (NamedType) type; if (!namedType.is

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>, propertyName, ctorType.getInstanceType().toString())); } } /** * Visits an ASSIGN node for cases such as * <pre> * interface.property2.property = ...; * </pre> */ private void visitInterfaceGetprop(NodeTraversal t, Node assign, Node object, String property, Node lvalue, Node rvalue) { JSType rvalueType = getJSType(rvalue); String abstractMethodName = compiler.getCodingConvention().getAbstractMethodName(); if (!rvalueType.isOrdinaryFunction() && !(rvalue.isQualifiedName() && rvalue.getQualifiedName().equals(abstractMethodName))) { compiler.report(JSError.make(t, object, INTERFACE_FUNCTION_MEMBERS_ONLY, abstractMethodName)); } if (assign.getLastChild().getType() == Token.FUNCTION && !NodeUtil.isEmptyBlock(assign.getLastChild().getLastChild())) { compiler.report(JSError.make(t, object, INTERFACE_FUNCTION_NOT_EMPTY, abstractMethodName)); } } /** * Visits an ASSIGN node for cases such as * <pre> * object.property = ...; * </pre> * that have an {@code @type} annotation. */ private void visitAnnotatedAssignGetprop(NodeTraversal t, Node assign, JSType type, Node object, String property, Node rvalue) { // verifying that the rvalue has the correct type validator.expectCanAssignToPropertyOf(t, assign, getJSType(rvalue), type, object, property); } /** * Visits a NAME node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. * @param parent The parent of the node n. * @return whether the node is typeable or not */ boolean visitName(NodeTraversal t, Node n, Node parent) { // At this stage, we need to determine whether this is a leaf // node in an expression (which therefore needs to have a type // assigned for it) versus some other decorative node that we // can safely ignore.

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> Function names, arguments (children of LP nodes) and // variable declarations are ignored. // TODO(user): remove this short-circuiting in favor of a // pre order traversal of the FUNCTION, CATCH, LP and VAR nodes. int parentNodeType = parent.getType(); if (parentNodeType == Token.FUNCTION || parentNodeType == Token.CATCH || parentNodeType == Token.LP || parentNodeType == Token.VAR) { return false; } JSType type = n.getJSType(); if (type == null) { type = getNativeType(UNKNOWN_TYPE); Var var = t.getScope().getVar(n.getString()); if (var != null) { JSType varType = var.getType(); if (varType != null) { type = varType; } } } ensureTyped(t, n, type); return true; } /** * Visits a GETPROP node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. * @param parent The parent of <code>n</code> */ private void visitGetProp(NodeTraversal t, Node n, Node parent) { // GETPROP nodes have an assigned type on their node by the scope creator // if this is an enum declaration. The only namespaced enum declarations // that we allow are of the form object.name = ...; if (n.getJSType() != null && parent.getType() == Token.ASSIGN) { return; } // obj.prop or obj.method() // Lots of types can appear on the left, a call to a void function can // never be on the left. getPropertyType will decide what is acceptable // and what isn't. Node property = n.getLastChild(); Node objNode = n.getFirstChild(); JSType childType = getJSType(objNode); // TODO(user): remove in favor of flagging every property access on // non-object. if (!validator.expectNotVoid(t, n, childType, "undefined has no properties", getNativeType(OBJECT_TYPE))) { ensureTyped(t, n);

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> return; } checkPropertyAccess(childType, property.getString(), t, n); ensureTyped(t, n); } /** * Make sure that the access of this property is ok. */ private void checkPropertyAccess(JSType childType, String propName, NodeTraversal t, Node n) { ObjectType objectType = childType.dereference(); if (objectType != null) { JSType propType = getJSType(n); if ((!objectType.hasProperty(propName) || objectType.equals(typeRegistry.getNativeType(UNKNOWN_TYPE))) && propType.equals(typeRegistry.getNativeType(UNKNOWN_TYPE))) { if (objectType instanceof EnumType) { t.report(n, INEXISTENT_ENUM_ELEMENT, propName); } else if (!objectType.isEmptyType() && reportMissingProperties && !isPropertyTest(n)) { if (!typeRegistry.canPropertyBeDefined(objectType, propName)) { t.report(n, INEXISTENT_PROPERTY, propName, validator.getReadableJSTypeName(n.getFirstChild(), true)); } } } } else { // TODO(nicksantos): might want to flag the access on a non object when // it's impossible to get a property from this type. } } /** * Determines whether this node is testing for the existence of a property. * If true, we will not emit warnings about a missing property. * * @param getProp The GETPROP being tested. */ private boolean isPropertyTest(Node getProp) { Node parent = getProp.getParent(); switch (parent.getType()) { case Token.CALL: return parent.getFirstChild() != getProp && compiler.getCodingConvention().isPropertyTestFunction(parent); case Token.IF: case Token.WHILE: case Token.DO: case Token.FOR: return NodeUtil.getConditionExpression(parent) == getProp; case Token.INSTANCEOF: case Token.TYPEOF: return true; case Token.AND: case Token.HOOK: return parent.getFirstChild() == getProp; } return false; } /** * Visits a GET

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> attach the UNKNOWN_TYPE. */ private void ensureTyped(NodeTraversal t, Node n) { ensureTyped(t, n, getNativeType(UNKNOWN_TYPE)); } private void ensureTyped(NodeTraversal t, Node n, JSTypeNative type) { ensureTyped(t, n, getNativeType(type)); } /** * Enforces type casts, and ensures the node is typed. * * A cast in the way that we use it in JSDoc annotations never * alters the generated code and therefore never can induce any runtime * operation. What this means is that a 'cast' is really just a compile * time constraint on the underlying value. In the future, we may add * support for run-time casts for compiled tests. * * To ensure some shred of sanity, we enforce the notion that the * type you are casting to may only meaningfully be a narrower type * than the underlying declared type. We also invalidate optimizations * on bad type casts. * * @param t The traversal object needed to report errors. * @param n The node getting a type assigned to it. * @param type The type to be assigned. */ private void ensureTyped(NodeTraversal t, Node n, JSType type) { // Make sure FUNCTION nodes always get function type. Preconditions.checkState(n.getType() != Token.FUNCTION || type instanceof FunctionType || type.isUnknownType()); JSDocInfo info = n.getJSDocInfo(); if (info != null) { if (info.hasType()) { JSType infoType = info.getType().evaluate(t.getScope()); validator.expectCanCast(t, n, infoType, type); type = infoType; } if (info.isImplicitCast() && !inExterns) { String propName = n.getType() == Token.GETPROP ? n.getLastChild().getString() : "(missing)"; compiler.report( JSError.make(t, n, ILLEGAL_IMPLICIT_CAST, propName)); } } if (n.getJSType() == null) { n.setJSType(type); } } /** * Returns the percentage of

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> }; } /** * Creates a new compiler pass to be run. */ final CompilerPass create(AbstractCompiler compiler) { Preconditions.checkState(!isCreated || !isOneTimePass, "One-time passes cannot be run multiple times: " + name); isCreated = true; return createInternal(compiler); } /** * Creates a new compiler pass to be run. */ abstract protected CompilerPass createInternal(AbstractCompiler compiler); }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>/* * Copyright 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.util.Set; /** * Models an assignment that defines a variable and the removal of it. * * */ class DefinitionsRemover { /** * @return an {@link Definition} object if the node contains a definition or * {@code null} otherwise. */ static Definition getDefinition(Node n, Node parent) { // TODO(user): Since we have parent pointers handy. A lot of constructors // can be simplied. if (parent == null) { return null; } if (NodeUtil.isVarDeclaration(n) && n.hasChildren()) { return new VarDefinition(n); } else if(NodeUtil.isFunction(parent) && parent.getFirstChild() == n) { if (!NodeUtil.isAnonymousFunction(parent)) { return new NamedFunctionDefinition(parent); } else if (!n.getString().equals("")) { return new AnonymousFunctionDefinition(parent); } } else if (NodeUtil.isAssign(parent) && parent.getFirstChild() == n) { return new AssignmentDefinition(parent); } else if (NodeUtil.isObjectLitKey(n, parent)) { return new ObjectLiteralPropertyDefinition(parent, n, n.getNext()); } else if (parent.getType() == Token.LP) { Node function = parent.getParent();

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> return new FunctionArgumentDefinition(function, n); } return null; } static interface Definition { void remove(); /** * Variable or property name represented by this definition. * For example, in the case of assignments this method would * return the NAME, GETPROP or GETELEM expression that acts as the * assignment left hand side. * * @return the L-Value associated with this definition. * The node's type is always NAME, GETPROP or GETELEM. */ Node getLValue(); /** * Value expression that acts as the right hand side of the * definition statement. */ Node getRValue(); } /** * Represents an name-only external definition. The definition's * rhs is missing. */ abstract static class IncompleteDefinition implements Definition { private static final Set<Integer> ALLOWED_TYPES = ImmutableSet.of(Token.NAME, Token.GETPROP, Token.GETELEM); private final Node lValue; IncompleteDefinition(Node lValue) { Preconditions.checkNotNull(lValue); Preconditions.checkArgument( ALLOWED_TYPES.contains(lValue.getType()), "Unexpected lValue type " + Token.name(lValue.getType())); this.lValue = lValue; } @Override public Node getLValue() { return lValue; } @Override public Node getRValue() { return null; } } /** * Represents an unknown definition. */ static final class UnknownDefinition extends IncompleteDefinition { UnknownDefinition(Node lValue) { super(lValue); } @Override public void remove() { throw new IllegalArgumentException("Can't remove an UnknownDefinition"); } } /** * Represents an name-only external definition. The definition's * rhs is missing. */ static final class ExternalNameOnlyDefinition extends IncompleteDefinition { ExternalNameOnlyDefinition(Node lValue) { super(lValue); } @Override public void remove() { throw new IllegalArgumentException( "Can't remove external name-only definition"); } } /** * Represents an name-only external definition. The definition's * rhs is missing. */ static final class FunctionArgumentDefinition extends IncompleteDefinition { Function

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>ArgumentDefinition(Node function, Node argumentName) { super(argumentName); Preconditions.checkArgument(NodeUtil.isFunction(function)); Preconditions.checkArgument(NodeUtil.isName(argumentName)); } @Override public void remove() { throw new IllegalArgumentException( "Can't remove a FunctionArgumentDefinition"); } } /** * Represents a function declaration or function expression. */ static abstract class FunctionDefinition implements Definition { protected final Node function; FunctionDefinition(Node node) { Preconditions.checkArgument(NodeUtil.isFunction(node)); function = node; } @Override public Node getLValue() { return function.getFirstChild(); } @Override public Node getRValue() { return function; } } /** * Represents a function declaration without assignment node such as * {@code function foo()}. */ static final class NamedFunctionDefinition extends FunctionDefinition { NamedFunctionDefinition(Node node) { super(node); } @Override public void remove() { function.detachFromParent(); } } /** * Represents a function expression that acts as a rhs. The defined * name is only reachable from within the function. */ static final class AnonymousFunctionDefinition extends FunctionDefinition { AnonymousFunctionDefinition(Node node) { super(node); Preconditions.checkArgument( NodeUtil.isAnonymousFunction(node)); } @Override public void remove() { // replace internal name with "" function.replaceChild(function.getFirstChild(), Node.newString(Token.NAME, "")); } } /** * Represents a declaration within an assignment. */ static final class AssignmentDefinition implements Definition { private final Node assignment; AssignmentDefinition(Node node) { Preconditions.checkArgument(NodeUtil.isAssign(node)); assignment = node; } @Override public void remove() { // A simple assignment. foo = bar() -> bar(); Node parent = assignment.getParent(); Node last = assignment.getLastChild(); assignment.removeChild(last); parent.replaceChild(assignment, last); } @Override public Node getLValue() { return assignment.getFirstChild(); } @Override public Node getRValue() { return assignment.getLastChild

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>(); } } /** * Represents member declarations using a object literal. * Example: var x = { e : function() { } }; */ static final class ObjectLiteralPropertyDefinition implements Definition { private final Node literal; private final Node name; private final Node value; ObjectLiteralPropertyDefinition(Node lit, Node name, Node value) { this.literal = lit; this.name = name; this.value = value; } @Override public void remove() { literal.removeChild(name); literal.removeChild(value); } @Override public Node getLValue() { // TODO(user) revisit: object literal definitions are an example // of definitions whose lhs doesn't correspond to a node that // exists in the AST. We will have to change the return type of // getLValue sooner or later in order to provide this added // flexibility. return new Node(Token.GETPROP, new Node(Token.OBJECTLIT), name.cloneNode()); } @Override public Node getRValue() { return value; } } /** * Represents a VAR declaration with an assignment. */ static final class VarDefinition implements Definition { private final Node name; VarDefinition(Node node) { Preconditions.checkArgument(NodeUtil.isVarDeclaration(node)); Preconditions.checkArgument(node.hasChildren(), "VAR Declaration of " + node.getString() + "should be assigned a value."); name = node; } @Override public void remove() { Node var = name.getParent(); Preconditions.checkState(var.getFirstChild() == var.getLastChild(), "AST should be normalized first"); Node parent = var.getParent(); Node rValue = name.removeFirstChild(); Preconditions.checkState(parent.getType() != Token.FOR); parent.replaceChild(var, NodeUtil.newExpr(rValue)); } @Override public Node getLValue() { return name; } @Override public Node getRValue() { return name.getFirstChild(); } } }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> + "\nCached : {0}\nActual : {1}"); static final DiagnosticType SCOPE_MISMATCH = DiagnosticType.error( "JSC_SCOPE_MISMATCH", "Scope roots used with the symbol table do not match." + "\nExpected : {0}\nActual : {1}"); private final AbstractCompiler compiler; private final ScopeCreator scopeCreator; // Mutex so that the symbol table may only be acquired by one pass // at a time. private boolean locked = false; // Memoized data with the pass that has currently acquired the // symbol table. private MemoizedData cache = null; SymbolTable(AbstractCompiler compiler) { this.compiler = compiler; compiler.addChangeHandler(this); scopeCreator = new SyntacticScopeCreator(compiler); } synchronized void acquire() { Preconditions.checkState(!locked, "SymbolTable already acquired"); locked = true; } synchronized void release() { Preconditions.checkState(locked, "SymbolTable already released"); locked = false; } /** * Returns the scope at the given node. */ @Override public Scope createScope(Node n, Scope parent) { // We may only ask for local blocks and the global (all scripts) block. Preconditions.checkArgument( (n.getType() == Token.BLOCK && n.getParent() == null) || n.getType() == Token.FUNCTION, "May only create scopes for the global node and functions"); ensureCacheInitialized(); if (!cache.scopes.containsKey(n)) { cache.scopes.put(n, scopeCreator.createScope(n, parent)); } return cache.scopes.get(n); } /** * Ensure that the memoization data structures have been initialized. */ private void ensureCacheInitialized() { Preconditions.checkState(locked, "Unacquired symbol table"); if (cache == null) { cache = new MemoizedData(); } } /** * If the AST changes, and the symbol table has not been acquired, then * all of our memoized data structures become stale. So delete them. */ @Override public void reportChange() { if (!locked) { cache = null; } } /** * All the

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> Preconditions.checkState(expectedScopes.size() == actualScopes.size()); for (int i = 0; i < expectedScopes.size(); i++) { Scope expectedScope = expectedScopes.get(i); Scope actualScope = actualScopes.get(i); if (!checkNodesMatch(expectedScope.getRootNode(), actualScope.getRootNode())) { compiler.report( JSError.make( SCOPE_MISMATCH, expectedScope.getRootNode().toStringTree(), actualScope.getRootNode().toStringTree())); continue; } if (expectedScope.getVarCount() != actualScope.getVarCount()) { compiler.report( JSError.make( VARIABLE_COUNT_MISMATCH, Integer.toString(expectedScope.getVarCount()), Integer.toString(actualScope.getVarCount()))); } else { Iterator<Var> it = expectedScope.getVars(); while (it.hasNext()) { Var var = it.next(); Scope.Var actualVar = actualScope.getVar(var.getName()); if (actualVar == null || expectedScope.getVar(var.getName()) != var) { compiler.report( JSError.make(MISSING_VARIABLE, var.getName())); } else if ( !checkNodesMatch( var.getNameNode(), actualVar.getNameNode()) || !isNodeAttached(actualVar.getNameNode())) { compiler.report( JSError.make(MOVED_VARIABLE, var.getName())); } } } } } /** * Check that the two nodes have the same relative position in the tree. */ private boolean checkNodesMatch(Node nodeA, Node nodeB) { Node currentA = nodeA; Node currentB = nodeB; while (currentA != null && currentB != null) { if (currentA.getType() != currentB.getType() || !currentA.isEquivalentTo(currentB)) { return false; } currentA = currentA.getParent(); currentB = currentB.getParent(); } return currentA == null && currentB == null; } private boolean isNodeAttached(Node node) { // Make sure the cached var is still attached. for (Node current = node; current != null; current

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>Doc(comment, info); } } } } // Only after we've seen all @fileoverview entries, attach the // last one to the root node, and copy the found license strings // to that node. if (fileOverviewInfo != null) { if ((irNode.getJSDocInfo() != null) && (irNode.getJSDocInfo().getLicense() != null)) { fileOverviewInfo.setLicense(irNode.getJSDocInfo().getLicense()); } irNode.setJSDocInfo(fileOverviewInfo); } } return irNode; } private Node transform(AstNode node) { String jsDoc = node.getJsDoc(); NodeWithJsDoc nodeWithJsDoc = null; if (jsDoc != null) { nodeWithJsDoc = new NodeWithJsDoc(); nodesWithJsDoc.put(jsDoc, nodeWithJsDoc); } Node irNode = justTransform(node); if (nodeWithJsDoc != null) { nodeWithJsDoc.node = irNode; } // If we have a named function, set the position to that of the name. if (irNode.getType() == Token.FUNCTION && irNode.getFirstChild().getLineno() != -1) { irNode.setLineno(irNode.getFirstChild().getLineno()); irNode.setCharno(irNode.getFirstChild().getCharno()); } else { if (irNode.getLineno() == -1) { // If we didn't already set the line, then set it now. This avoids // cases like ParenthesizedExpression where we just return a previous // node, but don't want the new node to get its parent's line number. int lineno = node.getLineno(); irNode.setLineno(lineno); int charno = position2charno(node.getAbsolutePosition()); irNode.setCharno(charno); } } return irNode; } /** * Creates a JsDocInfoParser and parses the JsDoc string. * * Used both for handling individual JSDoc comments and for handling * file-level JSDoc comments (@fileoverview and @license). *

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>().getString()); } @Override Node processBlock(Block blockNode) { return processGeneric(blockNode); } @Override Node processBreakStatement(BreakStatement statementNode) { Node node = new Node(Token.BREAK); if (statementNode.getBreakLabel() != null) { node.addChildToBack(transform(statementNode.getBreakLabel())); } return node; } @Override Node processCatchClause(CatchClause clauseNode) { AstNode catchVar = clauseNode.getVarName(); Node node = new Node(Token.CATCH, transform(catchVar)); if (clauseNode.getCatchCondition() != null) { node.addChildToBack(transform(clauseNode.getCatchCondition())); } else { Node catchCondition = new Node(Token.EMPTY); // Old Rhino used the position of the catchVar as the position // for the (nonexistent) error being caught. catchCondition.setLineno(catchVar.getLineno()); int clauseAbsolutePosition = position2charno(catchVar.getAbsolutePosition()); catchCondition.setCharno(clauseAbsolutePosition); node.addChildToBack(catchCondition); } node.addChildToBack(transform(clauseNode.getBody())); return node; } @Override Node processConditionalExpression(ConditionalExpression exprNode) { return new Node( Token.HOOK, transform(exprNode.getTestExpression()), transform(exprNode.getTrueExpression()), transform(exprNode.getFalseExpression())); } @Override Node processContinueStatement(ContinueStatement statementNode) { Node node = new Node(Token.CONTINUE); if (statementNode.getLabel() != null) { node.addChildToBack(transform(statementNode.getLabel())); } return node; } @Override Node processDoLoop(DoLoop loopNode) { return new Node( Token.DO, transform(loopNode.getBody()), transform(loopNode.getCondition())); } @Override Node processElementGet(ElementGet getNode) { return new Node( Token.GETELEM, transform(getNode.getTarget()), transform(getNode.getElement())); } @Override Node processEmptyExpression(EmptyExpression exprNode) { Node node = new Node

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_MUL: return Token.ASSIGN_MUL; case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_DIV: return Token.ASSIGN_DIV; case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_MOD: return Token.ASSIGN_MOD; case com.google.javascript.jscomp.mozilla.rhino.Token.HOOK: return Token.HOOK; case com.google.javascript.jscomp.mozilla.rhino.Token.COLON: return Token.COLON; case com.google.javascript.jscomp.mozilla.rhino.Token.OR: return Token.OR; case com.google.javascript.jscomp.mozilla.rhino.Token.AND: return Token.AND; case com.google.javascript.jscomp.mozilla.rhino.Token.INC: return Token.INC; case com.google.javascript.jscomp.mozilla.rhino.Token.DEC: return Token.DEC; case com.google.javascript.jscomp.mozilla.rhino.Token.DOT: return Token.DOT; case com.google.javascript.jscomp.mozilla.rhino.Token.FUNCTION: return Token.FUNCTION; case com.google.javascript.jscomp.mozilla.rhino.Token.EXPORT: return Token.EXPORT; case com.google.javascript.jscomp.mozilla.rhino.Token.IMPORT: return Token.IMPORT; case com.google.javascript.jscomp.mozilla.rhino.Token.IF: return Token.IF; case com.google.javascript.jscomp.mozilla.rhino.Token.ELSE: return Token.ELSE; case com.google.javascript.jscomp.mozilla.rhino.Token.SWITCH: return Token.SWITCH; case com.google.javascript.jscomp.mozilla.rhino.Token.CASE: return Token.CASE; case com.google.javascript.jscomp.mozilla.rhino.Token.DEFAULT: return Token.DEFAULT; case com.google.javascript.jscomp.mozilla.rhino.Token.WHILE: return Token.WHILE;

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>(source); assertEquals(expected, noSideEffectCalls); noSideEffectCalls.clear(); } @Override protected CompilerPass getProcessor(Compiler compiler) { return new NoSideEffectCallEnumerator(compiler); } /** * Run PureFunctionIdentifier, then gather a list of calls that are * marked as having no side effects. */ private class NoSideEffectCallEnumerator extends AbstractPostOrderCallback implements CompilerPass { private final Compiler compiler; NoSideEffectCallEnumerator(Compiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node root) { SimpleDefinitionFinder defFinder = new SimpleDefinitionFinder(compiler); defFinder.process(externs, root); PureFunctionIdentifier passUnderTest = new PureFunctionIdentifier(compiler, defFinder); passUnderTest.process(externs, root); // Ensure that debug report computation works. String debugReport = passUnderTest.getDebugReport(); NodeTraversal.traverse(compiler, externs, this); NodeTraversal.traverse(compiler, root, this); } public void visit(NodeTraversal t, Node n, Node parent) { if (n.getType() == Token.NEW) { if (!NodeUtil.constructorCallHasSideEffects(n)) { noSideEffectCalls.add(generateNameString(n.getFirstChild())); } } else if (n.getType() == Token.CALL) { if (!NodeUtil.functionCallHasSideEffects(n)) { noSideEffectCalls.add(generateNameString(n.getFirstChild())); } } } private String generateNameString(Node node) { if (node.getType() == Token.OR) { return "(" + generateNameString(node.getFirstChild()) + " || " + generateNameString(node.getLastChild()) + ")"; } else if (node.getType() == Token.HOOK) { return "(" + generateNameString(node.getFirstChild().getNext()) + " : " + generateNameString(node.getLastChild()) + ")"; } else { return node.getQualifiedName(); } } } }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>/* * Copyright 2007 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import static com.google.javascript.jscomp.SourceExcerptProvider.SourceExcerpt.LINE; import com.google.common.base.Preconditions; import com.google.javascript.jscomp.CheckLevel; import com.google.javascript.jscomp.SourceExcerptProvider.ExcerptFormatter; import com.google.javascript.jscomp.SourceExcerptProvider.SourceExcerpt; /** * Lightweight message formatter. The format of messages this formatter * produces is very compact and to the point. * * */ public class LightweightMessageFormatter extends AbstractMessageFormatter { private SourceExcerpt excerpt; private static final ExcerptFormatter excerptFormatter = new LineNumberingFormatter(); /** * A constructor for when the client doesn't care about source information. */ private LightweightMessageFormatter() { super(null); this.excerpt = LINE; } public LightweightMessageFormatter(SourceExcerptProvider source) { this(source, LINE); } public LightweightMessageFormatter(SourceExcerptProvider source, SourceExcerpt excerpt) { super(source); Preconditions.checkNotNull(source); this.excerpt = excerpt; } static LightweightMessageFormatter withoutSource() { return new LightweightMessageFormatter(); } public String formatError(JSError error) { return format(error, false); } public String formatWarning(JSError warning) { return format(warning, true); } private String format(JSError error, boolean warning) { // extract source excerpt SourceExcerptProvider

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>NativeProperty("toString"); } /** * Given the name of a native object property, checks whether the property is * present on the object and different from the native one. */ private boolean hasOverridenNativeProperty(String propertyName) { if (isNative()) { return false; } JSType propertyType = getPropertyType(propertyName); ObjectType nativeType = this.isFunctionType() ? registry.getNativeObjectType(JSTypeNative.FUNCTION_PROTOTYPE) : registry.getNativeObjectType(JSTypeNative.OBJECT_PROTOTYPE); JSType nativePropertyType = nativeType.getPropertyType(propertyName); return propertyType != nativePropertyType; } @Override public JSType unboxesTo() { if (isStringObjectType()) { return getNativeType(JSTypeNative.STRING_TYPE); } else if (isBooleanObjectType()) { return getNativeType(JSTypeNative.BOOLEAN_TYPE); } else if (isNumberObjectType()) { return getNativeType(JSTypeNative.NUMBER_TYPE); } else { return super.unboxesTo(); } } @Override public boolean matchesObjectContext() { return true; } @Override public boolean canBeCalled() { return isRegexpType(); } /** * Whether this represents a native type (such as Object, Date, * RegExp, etc.). */ boolean isNative() { return nativeType; } @Override public String toString() { return getReferenceName(); } @Override public FunctionType getConstructor() { return null; } @Override public ObjectType getImplicitPrototype() { return implicitPrototype; } /** * This should only be reset on the FunctionPrototypeType, only to fix an * incorrectly established prototype chain due to the user having a mismatch * in super class declaration, and only before properties on that type are * processed. */ void setImplicitPrototype(ObjectType implicitPrototype) { checkState(!hasCachedValues()); this.implicitPrototype = implicitPrototype; } @Override public String getReferenceName() { if (className != null) { return className; } else { return "{...}"; } } @Override public boolean hasReferenceName() { return className != null; }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>ErrorReporter.forNewRhino(this); /** Error strings used for reporting JSErrors */ public static final DiagnosticType OPTIMIZE_LOOP_ERROR = DiagnosticType.error( "JSC_OPTIMIZE_LOOP_ERROR", "Exceeded max number of optimization iterations: {0}"); public static final DiagnosticType MOTION_ITERATIONS_ERROR = DiagnosticType.error("JSC_OPTIMIZE_LOOP_ERROR", "Exceeded max number of code motion iterations: {0}"); private static final long COMPILER_STACK_SIZE = 1048576L; /** * Logger for the whole com.google.javascript.jscomp domain - * setting configuration for this logger affects all loggers * in other classes within the compiler. */ private static final Logger logger = Logger.getLogger("com.google.javascript.jscomp"); private final PrintStream outStream; /** * Creates a Compiler that reports errors and warnings to its logger. */ public Compiler() { this((PrintStream) null); } /** * Creates n Compiler that reports errors and warnings to an output * stream. */ public Compiler(PrintStream stream) { addChangeHandler(recentChange); this.typeValidator = new TypeValidator(this); outStream = stream; } /** * Creates a Compiler that uses a custom error manager. */ public Compiler(ErrorManager errorManager) { this(); setErrorManager(errorManager); } /** * Acquires the symbol table. */ @Override SymbolTable acquireSymbolTable() { if (symbolTable == null) { symbolTable = new SymbolTable(this); } symbolTable.acquire(); return symbolTable; } /** * Sets the error manager. * * @param errorManager the error manager, it cannot be {@code null} */ public void setErrorManager(ErrorManager errorManager) { Preconditions.checkNotNull( errorManager, "the error manager cannot be null"); this.errorManager = errorManager; } /** * Creates a message formatter instance corresponding to the value of * {@link CompilerOptions}. */ private MessageFormatter createMessageFormatter() { boolean colorize = options.shouldColorizeErrorOutput(); return options.errorFormat.

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> { String name = input.getName(); if (!inputsByName.containsKey(name)) { inputsByName.put(name, input); } else { report(JSError.make(DUPLICATE_INPUT, name)); } } } public Result compile( JSSourceFile extern, JSSourceFile input, CompilerOptions options) { return compile(extern, new JSSourceFile[] { input }, options); } public Result compile( JSSourceFile extern, JSSourceFile[] input, CompilerOptions options) { return compile(new JSSourceFile[] { extern }, input, options); } public Result compile( JSSourceFile extern, JSModule[] modules, CompilerOptions options) { return compile(new JSSourceFile[] { extern }, modules, options); } /** * Compiles a list of inputs. */ public Result compile(JSSourceFile[] externs, JSSourceFile[] inputs, CompilerOptions options) { // The compile method should only be called once. Preconditions.checkState(jsRoot == null); try { init(externs, inputs, options); if (hasErrors()) { return getResult(); } return compile(); } finally { Tracer t = newTracer("generateReport"); errorManager.generateReport(); stopTracer(t, "generateReport"); } } /** * Compiles a list of modules. */ public Result compile(JSSourceFile[] externs, JSModule[] modules, CompilerOptions options) { // The compile method should only be called once. Preconditions.checkState(jsRoot == null); try { init(externs, modules, options); if (hasErrors()) { return getResult(); } return compile(); } finally { Tracer t = newTracer("generateReport"); errorManager.generateReport(); stopTracer(t, "generateReport"); } } private Result compile() { return runInCompilerThread(new Callable<Result>() { public Result call() throws Exception { compileInternal(); return getResult(); } }); } /** * Disable threads. This is for clients that run on AppEngine and * don't have threads. */ public void disable

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>. if (!options.ideMode) { optimize(); } } if (options.recordFunctionInformation) { recordFunctionInformation(); } if (options.devMode == DevMode.START_AND_END) { runSanityCheck(); } } public void parse() { parseInputs(); } PassConfig getPassConfig() { if (passes == null) { passes = createPassConfigInternal(); } return passes; } /** * Create the passes object. Clients should use setPassConfig instead of * overriding this. */ PassConfig createPassConfigInternal() { return new DefaultPassConfig(options); } /** * @param passes The PassConfig to use with this Compiler. * @throws NullPointerException if passes is null * @throws IllegalStateException if this.passes has already been assigned */ public void setPassConfig(PassConfig passes) { // Important to check for null because if setPassConfig(null) is // called before this.passes is set, getPassConfig() will create a // new PassConfig object and use that, which is probably not what // the client wanted since he or she probably meant to use their // own PassConfig object. Preconditions.checkNotNull(passes); if (this.passes != null) { throw new IllegalStateException("this.passes has already been assigned"); } this.passes = passes; } /** * Carry out any special checks or procedures that need to be done before * proceeding with rest of the compilation process. * * @return true, to continue with compilation */ boolean precheck() { return true; } public void check() { runCustomPasses(CustomPassExecutionTime.BEFORE_CHECKS); PhaseOptimizer phaseOptimizer = new PhaseOptimizer(this, tracker); if (options.devMode == DevMode.EVERY_PASS) { phaseOptimizer.setSanityCheck(sanityCheck); } phaseOptimizer.consume(getPassConfig().getChecks()); phaseOptimizer.process(externsRoot, jsRoot); if (hasErrors()) { return; } // TODO(nicksantos): clean this up. The flow here is too hard to follow. if (options.nameAnonymousFunctionsOnly) { return;

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>> stripNamePrefixes) { logger.info("Strip code"); startPass("stripCode"); StripCode r = new StripCode(this, stripTypes, stripNameSuffixes, stripTypePrefixes, stripNamePrefixes); process(r); endPass(); } /** * Runs custom passes that are designated to run at a particular time. */ private void runCustomPasses(CustomPassExecutionTime executionTime) { if (options.customPasses != null) { Tracer t = newTracer("runCustomPasses"); try { for (CompilerPass p : options.customPasses.get(executionTime)) { process(p); } } finally { stopTracer(t, "runCustomPasses"); } } } private Tracer currentTracer = null; private String currentPassName = null; /** * Marks the beginning of a pass. */ void startPass(String passName) { Preconditions.checkState(currentTracer == null); currentPassName = passName; currentTracer = newTracer(passName); } /** * Marks the end of a pass. */ void endPass() { Preconditions.checkState(currentTracer != null, "Tracer should not be null at the end of a pass."); stopTracer(currentTracer, currentPassName); String passToCheck = currentPassName; currentPassName = null; currentTracer = null; maybeSanityCheck(); } /** * Returns a new tracer for the given pass name. */ Tracer newTracer(String passName) { String comment = passName + (recentChange.hasCodeChanged() ? " on recently changed AST" : ""); if (options.tracer.isOn()) { tracker.recordPassStart(passName); } return new Tracer("Compiler", comment); } void stopTracer(Tracer t, String passName) { long result = t.stop(); if (options.tracer.isOn()) { tracker.recordPassStop(passName, result); } } /** * Returns the result of the compilation. */ public Result getResult() { PassConfig.State state = getPassConfig().getIntermediateState(); return new Result(getErrors(),

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> } } //------------------------------------------------------------------------ // Inputs //------------------------------------------------------------------------ // TODO(nicksantos): Decide which parts of these belong in an AbstractCompiler // interface, and which ones should always be injected. @Override public CompilerInput getInput(String name) { return inputsByName.get(name); } @Override public CompilerInput newExternInput(String name) { if (inputsByName.containsKey(name)) { throw new IllegalArgumentException("Conflicting externs name: " + name); } SourceAst ast = new SyntheticAst(name); CompilerInput input = new CompilerInput(ast, name, true); inputsByName.put(name, input); externsRoot.addChildToFront(ast.getAstRoot(this)); return input; } /** Add a source input dynamically. Intended for incremental compilation. */ void addIncrementalSourceAst(JsAst ast) { String sourceName = ast.getSourceFile().getName(); Preconditions.checkState( getInput(sourceName) == null, "Duplicate input of name " + sourceName); inputsByName.put(sourceName, new CompilerInput(ast)); } @Override JSModuleGraph getModuleGraph() { return moduleGraph; } @Override public JSTypeRegistry getTypeRegistry() { if (typeRegistry == null) { typeRegistry = new JSTypeRegistry(oldErrorReporter); } return typeRegistry; } @Override ScopeCreator getScopeCreator() { return getPassConfig().getScopeCreator(); } @Override public Scope getTopScope() { return getPassConfig().getTopScope(); } @Override public ReverseAbstractInterpreter getReverseAbstractInterpreter() { if (abstractInterpreter == null) { ChainableReverseAbstractInterpreter interpreter = new SemanticReverseAbstractInterpreter( getCodingConvention(), getTypeRegistry()); if (options.closurePass) { interpreter = new ClosureReverseAbstractInterpreter( getCodingConvention(), getTypeRegistry()) .append(interpreter).getFirst(); } abstractInterpreter = interpreter; } return abstractInterpreter; } @Override TypeValidator getTypeValidator() { return typeValidator; } //------------------------------------------------------------------------ // Parsing //------------------------------------------------------------------------ /** * Parses the externs and main inputs. * * @

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> IllegalArgumentException( "Bad module input: " + inputs.get(i).getName()); } cb.reset(); toSource(cb, i, scriptNode); sources[i] = cb.toString(); } return sources; } }); } /** * Writes out js code from a root node. If printing input delimiters, this * method will attach a comment to the start of the text indicating which * input the output derived from. If there were any preserve annotations * within the root's source, they will also be printed in a block comment * at the beginning of the output. */ public void toSource(final CodeBuilder cb, final int inputSeqNum, final Node root) { runInCompilerThread(new Callable<Void>() { public Void call() throws Exception { if (options.printInputDelimiter) { if ((cb.getLength() > 0) && !cb.endsWith("\n")) { cb.append("\n"); // Make sure that the label starts on a new line } Preconditions.checkState(root.getType() == Token.SCRIPT); String delimiter = options.inputDelimiter; String sourceName = (String)root.getProp(Node.SOURCENAME_PROP); Preconditions.checkState(sourceName != null); Preconditions.checkState(!sourceName.isEmpty()); delimiter = delimiter.replaceAll("%name%", sourceName) .replaceAll("%num%", String.valueOf(inputSeqNum)); cb.append(delimiter) .append("\n"); } if (root.getJSDocInfo() != null && root.getJSDocInfo().getLicense() != null) { cb.append("/*\n") .append(root.getJSDocInfo().getLicense()) .append("*/\n"); } // If there is a valid source map, then indicate to it that the current // root node's mappings are offset by the given string builder buffer. if (options.sourceMapOutputPath != null) { sourceMap.setStartingPosition( cb.getLineIndex(), cb.getColumnIndex()); } String code = toSource(root); if (!code.isEmpty()) { cb.append(code); if (!code.endsWith(";")) { cb.append(";"); }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>/* * Copyright 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.javascript.jscomp.parsing.ParserRunner; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.io.IOException; import java.util.logging.Logger; /** * Generates an AST for a JavaScript source file. * * */ public class JsAst implements SourceAst { private static final Logger logger_ = Logger.getLogger(JsAst.class.getName()); private static final long serialVersionUID = 1L; private transient SourceFile sourceFile; private String fileName; private Node root; public JsAst(SourceFile sourceFile) { this.sourceFile = sourceFile; this.fileName = sourceFile.getName(); } @Override public Node getAstRoot(AbstractCompiler compiler) { if (root == null) { createAst(compiler); } return root; } @Override public void clearAst() { root = null; // While we're at it, clear out any saved text in the source file on // the assumption that if we're dumping the parse tree, then we probably // assume regenerating everything else is a smart idea also. sourceFile.clearCachedSource(); } @Override public SourceFile getSourceFile() { return sourceFile; } @Override public void setSourceFile(SourceFile file) { Preconditions.checkState(fileName.equals(file.getName())); sourceFile = file; } private void createAst(AbstractCompiler compiler)

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>Group ...groups) { Set<DiagnosticType> set = Sets.newHashSet(); for (DiagnosticGroup group : groups) { set.addAll(group.types); } this.types = ImmutableSet.copyOf(set); } /** * Returns whether the given error's type matches a type * in this group. */ public boolean matches(JSError error) { return matches(error.getType()); } /** * Returns whether the given type matches a type in this group. */ public boolean matches(DiagnosticType type) { return types.contains(type); } /** * Returns whether all of the types in the given group are in this group. */ boolean isSubGroup(DiagnosticGroup group) { for (DiagnosticType type : group.types) { if (!matches(type)) { return false; } } return true; } /** * Returns an iterator over all the types in this group. */ Collection<DiagnosticType> getTypes() { return types; } }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> /** * Create a named type based on the reference. */ public NamedType(JSTypeRegistry registry, String reference, String sourceName, int lineno, int charno) { super(registry, registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE)); Preconditions.checkNotNull(reference); this.reference = reference; this.sourceName = sourceName; this.lineno = lineno; this.charno = charno; } @Override public void forgiveUnknownNames() { forgiving = true; } /** Returns the type to which this refers (which is unknown if unresolved). */ public JSType getReferencedType() { return referencedType; } @Override public String getReferenceName() { return reference; } @Override public String toString() { return reference; } @Override public boolean hasReferenceName() { return true; } @Override public boolean isNamedType() { return true; } @Override public boolean isNominalType() { return true; } /** * Two named types are equal if they are the same {@code ObjectType} object. * This is complicated by the fact that equals is sometimes called before we * have a chance to resolve the type names. * * @return {@code true} iff {@code that} == {@code this} or {@code that} * is a {@link NamedType} whose reference is the same as ours, * or {@code that} is the type we reference. */ @Override public boolean equals(Object that) { if (this == that) { return true; } else if (that instanceof JSType) { ObjectType objType = ObjectType.cast((JSType) that); if (objType != null) { return objType.isNominalType() && reference.equals(objType.getReferenceName()); } } return false; } @Override public int hashCode() { return reference.hashCode(); } /** * Resolve the referenced type within the enclosing scope. */ @Override JSType resolveInternal(ErrorReporter t, StaticScope<JSType> enclosing) { // TODO(user): Investigate whether it is really necessary to keep two // different

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> null; if (parent == null) { scope = new Scope(n, compiler); } else { scope = new Scope(parent, n); } scanRoot(n, parent); sourceName = null; Scope returnedScope = scope; scope = null; return returnedScope; } private void scanRoot(Node n, Scope parent) { if (n.getType() == Token.FUNCTION) { sourceName = (String) n.getProp(Node.SOURCENAME_PROP); final Node fnNameNode = n.getFirstChild(); final Node args = fnNameNode.getNext(); final Node body = args.getNext(); // Bleed the function name into the scope, if it hasn't // been declared in the outer scope. String fnName = fnNameNode.getString(); if (!fnName.isEmpty() && NodeUtil.isFunctionAnonymous(n)) { declareVar(fnName, fnNameNode, n, null, null, n); } // Args: Declare function variables Preconditions.checkState(args.getType() == Token.LP); for (Node a = args.getFirstChild(); a != null; a = a.getNext()) { Preconditions.checkState(a.getType() == Token.NAME); declareVar(a.getString(), a, args, n, null, n); } // Body scanVars(body, n); } else { // It's the global block Preconditions.checkState(scope.getParent() == null); scanVars(n, null); } } /** * Scans and gather variables declarations under a Node */ private void scanVars(Node n, Node parent) { switch (n.getType()) { case Token.VAR: // Declare all variables. e.g. var x = 1, y, z; for (Node child = n.getFirstChild(); child != null;) { Node next = child.getNext(); Preconditions.checkState(child.getType() == Token.NAME); String name = child.getString(); declareVar(name, child, n, parent, null, n); child = next; } return; case Token.FUNCTION: if (NodeUtil.isFunctionAnonymous(n)) { return; }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> String fnName = n.getFirstChild().getString(); if (fnName.isEmpty()) { // This is invalid, but allow it so the checks can catch it. return; } declareVar(fnName, n.getFirstChild(), n, parent, null, n); return; // should not examine function's children case Token.CATCH: Preconditions.checkState(n.getChildCount() == 3); Preconditions.checkState(n.getFirstChild().getType() == Token.NAME); // the first child is the catch var and the third child // is the code block final Node var = n.getFirstChild(); final Node block = var.getNext().getNext(); declareVar(var.getString(), var, n, parent, null, n); scanVars(block, n); return; // only one child to scan case Token.SCRIPT: sourceName = (String) n.getProp(Node.SOURCENAME_PROP); break; } // Variables can only occur in statement-level nodes, so // we only need to traverse children in a couple special cases. if (NodeUtil.isControlStructure(n) || NodeUtil.isStatementBlock(n)) { for (Node child = n.getFirstChild(); child != null;) { Node next = child.getNext(); scanVars(child, n); child = next; } } } /** * Interface for injectable duplicate handling. */ interface RedeclarationHandler { void onRedeclaration( Scope s, String name, Node n, Node parent, Node gramps, Node nodeWithLineNumber); } /** * The default handler for duplicate declarations. */ private class DefaultRedeclarationHandler implements RedeclarationHandler { public void onRedeclaration( Scope s, String name, Node n, Node parent, Node gramps, Node nodeWithLineNumber) { // Don't allow multiple variables to be declared at the top level scope if (scope.isGlobal()) { Scope.Var origVar = scope.getVar(name); Node origParent = origVar.getParentNode(); if (origParent.getType() == Token.CATCH && parent.getType() == Token.CATCH) { // Okay, both are 'catch(x)' variables. return

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> } static interface JoinOp<L extends LatticeElement> extends Function<List<L>, L> { } /** * An implementation of {@code JoinOp} that makes it easy to join to * lattice elements at a time. */ static abstract class BinaryJoinOp<L extends LatticeElement> implements JoinOp<L> { @Override public final L apply(List<L> values) { Preconditions.checkArgument(!values.isEmpty()); int size = values.size(); if (size == 1) { return values.get(0); } else if (size == 2) { return apply(values.get(0), values.get(1)); } else { int mid = computeMidPoint(size); return apply( apply(values.subList(0, mid)), apply(values.subList(mid, size))); } } /** * Creates a new lattice that will be the join of two input lattices. * * @return The join of {@code latticeA} and {@code latticeB}. */ abstract L apply(L latticeA, L latticeB); /** * Finds the midpoint of a list. The function will favor two lists of * even length instead of two lists of the same odd length. The list * must be at least length two. * * @param size Size of the list. */ static int computeMidPoint(int size) { int midpoint = size >>> 1; if (size > 4) { /* Any list longer than 4 should prefer an even split point * over the true midpoint, so that [0,6] splits at 2, not 3. */ midpoint &= -2; // (0xfffffffe) clears low bit so midpoint is even } return midpoint; } } private final ControlFlowGraph<N> cfg; final JoinOp<L> joinOp; protected final Set<DiGraphNode<N, Branch>> orderedWorkSet; /* * Feel free to increase this to a reasonable number if you are finding that * more and more passes need more than 100000 steps before finding a * fixed-point. If you just have a special case, consider calling *

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> private L out; /** * Private constructor. No other classes should create new states. * * @param inState Input. * @param outState Output. */ private FlowState(L inState, L outState) { Preconditions.checkNotNull(inState); Preconditions.checkNotNull(outState); this.in = inState; this.out = outState; } L getIn() { return in; } void setIn(L in) { Preconditions.checkNotNull(in); this.in = in; } L getOut() { return out; } void setOut(L out) { Preconditions.checkNotNull(out); this.out = out; } @Override public String toString() { return String.format("IN: %s OUT: %s", in, out); } @Override public int hashCode() { return Objects.hashCode(in, out); } } /** * The exception to be thrown if the analysis has been running for a long * number of iterations. Chances are the analysis is not monotonic, a * fixed-point cannot be found and it is currently stuck in an infinite loop. */ static class MaxIterationsExceededException extends RuntimeException { private static final long serialVersionUID = 1L; MaxIterationsExceededException(String msg) { super(msg); } } abstract static class BranchedForwardDataFlowAnalysis <N, L extends LatticeElement> extends DataFlowAnalysis<N, L> { @Override protected void initialize() { orderedWorkSet.clear(); for (DiGraphNode<N, Branch> node : getCfg().getDirectedGraphNodes()) { List<DiGraphEdge<N, Branch>> edgeList = getCfg().getOutEdges(node.getValue()); int outEdgeCount = edgeList.size(); List<L> outLattices = Lists.newArrayList(); for (int i = 0; i < outEdgeCount; i++) { outLattices.add(createInitialEstimateLattice()); } node.setAnnotation(new BranchedFlowState<L>( createInitialEstimateLattice(), outLattices)); if (node != getCfg().getImplicitReturn()) { orderedWorkSet

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>.add(node); } } } BranchedForwardDataFlowAnalysis(ControlFlowGraph<N> targetCfg, JoinOp<L> joinOp) { super(targetCfg, joinOp); } /** * Returns the lattice element at the exit point. Needs to be overridden * because we use a BranchedFlowState instead of a FlowState; ugh. */ @Override L getExitLatticeElement() { DiGraphNode<N, Branch> node = getCfg().getImplicitReturn(); BranchedFlowState<L> state = node.getAnnotation(); return state.getIn(); } @Override final boolean isForward() { return true; } /** * The branched flow function maps a single lattice to a list of output * lattices. * * <p>Each outgoing edge of a node will have a corresponding output lattice * in the ordered returned by * {@link com.google.javascript.jscomp.graph.DiGraph#getOutEdges(Object)} * in the returned list. * * @return A list of output values depending on the edge's branch type. */ abstract List<L> branchedFlowThrough(N node, L input); @Override protected final boolean flow(DiGraphNode<N, Branch> node) { BranchedFlowState<L> state = node.getAnnotation(); List<L> outBefore = state.out; state.out = branchedFlowThrough(node.getValue(), state.in); Preconditions.checkState(outBefore.size() == state.out.size()); for (int i = 0; i < outBefore.size(); i++) { if (!outBefore.get(i).equals(state.out.get(i))) { return true; } } return false; } @Override protected void joinInputs(DiGraphNode<N, Branch> node) { BranchedFlowState<L> state = node.getAnnotation(); List<DiGraphNode<N, Branch>> predNodes = getCfg().getDirectedPredNodes(node); List<L> values = new ArrayList<L>(predNodes.size()); for (DiGraphNode<N, Branch> predNode :

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> predNodes) { BranchedFlowState<L> predNodeState = predNode.getAnnotation(); L in = predNodeState.out.get( getCfg().getDirectedSuccNodes(predNode).indexOf(node)); values.add(in); } if (getCfg().getEntry() == node) { state.setIn(createEntryLattice()); } else if (!values.isEmpty()) { state.setIn(joinOp.apply(values)); } } } /** * The in and out states of a node. * * @param <L> Input and output lattice element type. */ static class BranchedFlowState<L extends LatticeElement> implements Annotation { private L in; private List<L> out; /** * Private constructor. No other classes should create new states. * * @param inState Input. * @param outState Output. */ private BranchedFlowState(L inState, List<L> outState) { Preconditions.checkNotNull(inState); Preconditions.checkNotNull(outState); this.in = inState; this.out = outState; } L getIn() { return in; } void setIn(L in) { Preconditions.checkNotNull(in); this.in = in; } List<L> getOut() { return out; } void setOut(List<L> out) { Preconditions.checkContentsNotNull(out); this.out = out; } @Override public String toString() { return String.format("IN: %s OUT: %s", in, out); } @Override public int hashCode() { return Objects.hashCode(in, out); } } /** * Compute set of escaped variables. When a variable is escaped in a * dataflow analysis, it can be reference outside of the code that we are * analyzing. A variable is escaped if any of the following is true: * * <p><ol> * <li>It is defined as the exception name in CATCH clause so it became a * variable local not to our definition of scope.</li> * <li>Exported variables as they can be needed after the script terminates

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>/* * Copyright 2004 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.TokenStream; /** * Utility class that extracts the qualified name out of a node. * Useful when trying to get a human-friendly string representation of * a property node that can be used to describe the node or name * related nodes based on it (as done by the NameAnonymousFunctions * compiler pass). * * */ class NodeNameExtractor { private final char delimiter; private int nextUniqueInt = 0; NodeNameExtractor(char delimiter) { this.delimiter = delimiter; } /** * Returns a qualified name of the specified node. Dots and brackets * are changed to the delimiter passed in when constructing the * NodeNameExtractor object. We also replace ".prototype" with the * delimiter to keep names short, while still differentiating them * from static properties. (Prototype properties will end up * looking like "a$b$$c" if this.delimiter = '$'.) */ String getName(Node node) { switch (node.getType()) { case Token.FUNCTION: Node functionNameNode = node.getFirstChild(); return functionNameNode.getString(); case Token.GETPROP: Node lhsOfDot = node.getFirstChild(); Node rhsOfDot = lhsOfDot.getNext(); String lhsOfDotName = getName(lhsOfDot); String rhsOfDotName = getName(rhsOfDot); if ("prototype".equals(rhsOfDot

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> LB = 79, // left and right brackets RB = 80, LC = 81, // left and right curlies (braces) RC = 82, LP = 83, // left and right parentheses RP = 84, COMMA = 85, // comma operator ASSIGN = 86, // simple assignment (=) ASSIGN_BITOR = 87, // |= ASSIGN_BITXOR = 88, // ^= ASSIGN_BITAND = 89, // |= ASSIGN_LSH = 90, // <<= ASSIGN_RSH = 91, // >>= ASSIGN_URSH = 92, // >>>= ASSIGN_ADD = 93, // += ASSIGN_SUB = 94, // -= ASSIGN_MUL = 95, // *= ASSIGN_DIV = 96, // /= ASSIGN_MOD = 97; // %= public final static int FIRST_ASSIGN = ASSIGN, LAST_ASSIGN = ASSIGN_MOD, HOOK = 98, // conditional (?:) COLON = 99, OR = 100, // logical or (||) AND = 101, // logical and (&&) INC = 102, // increment/decrement (++ --) DEC = 103, DOT = 104, // member operator (.) FUNCTION = 105, // function keyword EXPORT = 106, // export keyword IMPORT = 107, // import keyword IF = 108, // if keyword ELSE = 109, // else keyword SWITCH = 110, // switch keyword CASE = 111, // case keyword DEFAULT = 112, // default keyword WHILE = 113, // while keyword DO = 114, // do keyword FOR = 11

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> case REF_NAME: return "REF_NAME"; case REF_NS_NAME: return "REF_NS_NAME"; case TRY: return "TRY"; case SEMI: return "SEMI"; case LB: return "LB"; case RB: return "RB"; case LC: return "LC"; case RC: return "RC"; case LP: return "LP"; case RP: return "RP"; case COMMA: return "COMMA"; case ASSIGN: return "ASSIGN"; case ASSIGN_BITOR: return "ASSIGN_BITOR"; case ASSIGN_BITXOR: return "ASSIGN_BITXOR"; case ASSIGN_BITAND: return "ASSIGN_BITAND"; case ASSIGN_LSH: return "ASSIGN_LSH"; case ASSIGN_RSH: return "ASSIGN_RSH"; case ASSIGN_URSH: return "ASSIGN_URSH"; case ASSIGN_ADD: return "ASSIGN_ADD"; case ASSIGN_SUB: return "ASSIGN_SUB"; case ASSIGN_MUL: return "ASSIGN_MUL"; case ASSIGN_DIV: return "ASSIGN_DIV"; case ASSIGN_MOD: return "ASSIGN_MOD"; case HOOK: return "HOOK"; case COLON: return "COLON"; case OR: return "OR"; case AND: return "AND"; case INC: return "INC"; case DEC: return "DEC"; case DOT: return "DOT"; case FUNCTION: return "FUNCTION"; case EXPORT: return "EXPORT"; case IMPORT: return "IMPORT"; case IF: return "IF"; case ELSE: return "ELSE"; case SWITCH: return "SWITCH"; case CASE: return "CASE"; case DEFAULT: return "DEFAULT"; case WHILE: return "WHILE"; case DO: return "DO"; case FOR: return "FOR"; case BREAK: return "BREAK"; case CONTINUE: return "CONTINUE"; case VAR: return "VAR"; case WITH: return "WITH"; case CATCH: return "CATCH"; case

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>. * @return A clone of the function body mutated to be suitable for injection * as a statement into another code block. */ Node mutate(String fnName, Node fnNode, Node callNode, String resultName, boolean needsDefaultResult, boolean isCallInLoop) { Node newFnNode = fnNode.cloneTree(); // Now that parameter names have been replaced, make sure all the local // names are unique, to allow functions to be inlined multiple times // without causing conflicts. makeLocalNamesUnique(newFnNode, isCallInLoop); // TODO(johnlenz): Mark NAME nodes constant for parameters that are not // modified. Set<String> namesToAlias = FunctionArgumentInjector.findModifiedParameters(newFnNode); LinkedHashMap<String, Node> args = FunctionArgumentInjector.getFunctionCallParameterMap( newFnNode, callNode, this.safeNameIdSupplier); boolean hasArgs = !args.isEmpty(); if (hasArgs) { FunctionArgumentInjector.maybeAddTempsForCallArguments( newFnNode, args, namesToAlias, compiler.getCodingConvention()); } Node newBlock = NodeUtil.getFunctionBody(newFnNode); // Make the newBlock insertable . newBlock.detachFromParent(); if (hasArgs) { Node inlineResult = aliasAndInlineArguments(newBlock, args, namesToAlias); Preconditions.checkState(newBlock == inlineResult); } // // For calls inlined into loops, VAR declarations are not reinitialized to // undefined as they would have been if the function were called, so ensure // that they are properly initialized. // if (isCallInLoop) { fixUnitializedVarDeclarations(newBlock); } String labelName = getLabelNameForFunction(fnName); Node injectableBlock = replaceReturns( newBlock, resultName, labelName, needsDefaultResult); Preconditions.checkState(injectableBlock != null); return injectableBlock; } /** * For all VAR node with uninitialized declarations, set * the values to be "undefined". */ private void fixUnitializedVarDeclarations(Node n) { // Inner loop structure must already have logic to initialize its // variables. In

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> particular FOR-IN structures must not be modified. if (NodeUtil.isLoopStructure(n)) { return; } // For all VARs if (NodeUtil.isVar(n)) { Node name = n.getFirstChild(); // It isn't initialized. if (!name.hasChildren()) { name.addChildToBack(NodeUtil.newUndefinedNode()); } return; } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { fixUnitializedVarDeclarations(c); } } /** * Fix-up all local names to be unique for this subtree. * @param fnNode A mutable instance of the function to be inlined. */ private void makeLocalNamesUnique(Node fnNode, boolean isCallInLoop) { NodeTraversal.traverse( compiler, fnNode, new MakeDeclaredNamesUnique( new InlineRenamer( compiler.getUniqueNameIdSupplier(), "inline_", isCallInLoop))); } /** * Create a unique label name. */ private String getLabelNameForFunction(String fnName){ String name = (fnName == null || fnName.isEmpty()) ? "anon" : fnName; return "JSCompiler_inline_label_" + name + "_" + safeNameIdSupplier.get(); } /** * Inlines the arguments within the node tree using the given argument map, * replaces "unsafe" names with local aliases. * * The aliases for unsafe require new VAR declarations, so this function * can not be used in for direct CALL node replacement as VAR nodes can not be * created there. * * @return The node or its replacement. */ private Node aliasAndInlineArguments( Node fnTemplateRoot, LinkedHashMap<String, Node> argMap, Set<String> namesToAlias) { if (namesToAlias == null || namesToAlias.isEmpty()) { // There are no names to alias, just inline the arguments directly. Node result = FunctionArgumentInjector.inject( fnTemplateRoot, null, argMap); Preconditions.checkState(result == fnTemplateRoot); return result; } else { // Create local alias of names that can not be safely // used directly. // An arg map that will

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> be updated to contain the // safe aliases. Map<String, Node> newArgMap = Maps.newHashMap(argMap); // Declare the alias in the same order as they // are declared. List<Node> newVars = Lists.newLinkedList(); // NOTE: argMap is a linked map so we get the parameters in the // order that they were declared. for (Entry<String, Node> entry : argMap.entrySet()) { String name = entry.getKey(); if (namesToAlias.contains(name)) { Node newValue = entry.getValue().cloneTree(); Node newNode = NodeUtil.newVarNode(name, newValue); newVars.add(0, newNode); // Remove the parameter from the list to replace. newArgMap.remove(name); } } // Inline the arguments. Node result = FunctionArgumentInjector.inject( fnTemplateRoot, null, newArgMap); Preconditions.checkState(result == fnTemplateRoot); // Now that the names have been replaced, add the new aliases for // the old names. for (Node n : newVars) { fnTemplateRoot.addChildToFront(n); } return result; } } /** * Convert returns to assignments and breaks, as needed. * For example, with a lableName of 'foo': * { * return a; * } * becomes: * foo: { * a; * break foo; * } * or * foo: { * resultName = a; * break foo; * } * * @param resultMustBeSet Whether the result must always be set to a value. * @return The node containing the transformed block, this may be different * than the passed in node 'block'. */ private static Node replaceReturns( Node block, String resultName, String labelName, boolean resultMustBeSet) { Preconditions.checkNotNull(block); Preconditions.checkNotNull(labelName); Node root = block; boolean hasReturnAtExit = false; int returnCount = NodeUtil.getNodeTypeReferenceCount(block, Token.RETURN); if (returnCount > 0) { hasReturnAtExit

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> = hasReturnAtExit(block); // TODO(johnlenz): Simpler not to special case this, // and let it be optimized later. if (hasReturnAtExit) { convertLastReturnToStatement(block, resultName); returnCount--; } if (returnCount > 0) { // A label and breaks are needed. // Add the breaks replaceReturnWithBreak(block, null, resultName, labelName); // Add label Node label = new Node(Token.LABEL); Node name = Node.newString(Token.NAME, labelName); label.addChildToFront(name); label.addChildToBack(block); Node newRoot = new Node(Token.BLOCK); newRoot.addChildrenToBack(label); // The label is now the root. root = newRoot; } } // If there wasn't an return at the end of the function block, and we need // a result, add one to the block. if (resultMustBeSet && !hasReturnAtExit && resultName != null) { addDummyAssignment(block, resultName); } return root; } /********************************************************************** * Functions following here are general node transformation functions **********************************************************************/ /** * Example: * a = (void) 0; */ private static void addDummyAssignment(Node node, String resultName) { Preconditions.checkArgument(node.getType() == Token.BLOCK); // A result is needed create a dummy value. Node retVal = NodeUtil.newUndefinedNode(); Node resultNode = createAssignStatementNode(resultName, retVal); node.addChildrenToBack(resultNode); } /** * Replace the 'return' statement with its child expression. * "return foo()" becomes "foo()" or "resultName = foo()" * "return" is removed or becomes "resultName = void 0". * * @param block * @param resultName */ private static void convertLastReturnToStatement( Node block, String resultName) { Node ret = block.getLastChild(); Preconditions.checkArgument(ret.getType() == Token.RETURN); Node resultNode = getReplacementReturnStatement(ret, resultName); if (resultNode == null)

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>(); break;}" * "return" becomes {break;} or "{resultName = void 0;break;}". */ private static Node replaceReturnWithBreak(Node current, Node parent, String resultName, String labelName) { if (current.getType() == Token.FUNCTION || current.getType() == Token.EXPR_RESULT) { // Don't recurse into functions definitions, and expressions can't // contain RETURN nodes. return current; } if (current.getType() == Token.RETURN) { Preconditions.checkState(NodeUtil.isStatementBlock(parent)); Node resultNode = getReplacementReturnStatement(current, resultName); Node name = Node.newString(Token.NAME, labelName); Node breakNode = new Node(Token.BREAK, name); // Replace the node in parent, and reset current to the first new child. parent.replaceChild(current, breakNode); if (resultNode != null) { parent.addChildBefore(resultNode, breakNode); } current = breakNode; } else { for (Node c = current.getFirstChild(); c != null; c = c.getNext()) { // c may be replaced. c = replaceReturnWithBreak(c, current, resultName, labelName); } } return current; } }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>/* * Copyright 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.jstype.FunctionType; import com.google.javascript.rhino.jstype.JSType; import com.google.javascript.rhino.jstype.ObjectType; import java.nio.charset.Charset; /** * A code generator that outputs type annotations for functions and * constructors. * */ class TypedCodeGenerator extends CodeGenerator { TypedCodeGenerator(CodeConsumer consumer, Charset outputCharset) { super(consumer, outputCharset, true); } @Override void add(Node n, Context context) { Node parent = n.getParent(); if (parent.getType() == Token.BLOCK || parent.getType() == Token.SCRIPT) { if (n.getType() == Token.FUNCTION) { add(getFunctionAnnotation(n)); } else if (n.getType() == Token.EXPR_RESULT && n.getFirstChild().getType() == Token.ASSIGN) { Node rhs = n.getFirstChild().getFirstChild(); add(getTypeAnnotation(rhs)); } else if (n.getType() == Token.VAR && n.getFirstChild().getFirstChild() != null && n.getFirstChild().getFirstChild().getType() == Token.FUNCTION) { add(getFunctionAnnotation(n.getFirstChild().getFirstChild())); } } super.add(n, context); } private String getTypeAnnotation(Node node) { JSType type = node.getJSType();

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> { this.rootRenamer = new ContextualRenamer(); } MakeDeclaredNamesUnique(Renamer renamer) { this.rootRenamer = renamer; } static CompilerPass getContextualRenameInverter(AbstractCompiler compiler) { return new ContextualRenameInverter(compiler); } @Override public void enterScope(NodeTraversal t) { Node declarationRoot = t.getScopeRoot(); Renamer renamer; if (nameStack.isEmpty()) { // If the contextual renamer is being used the starting context can not // be a function. Preconditions.checkState( declarationRoot.getType() != Token.FUNCTION || !(rootRenamer instanceof ContextualRenamer)); Preconditions.checkState(t.inGlobalScope()); renamer = rootRenamer; } else { renamer = nameStack.peek().forChildScope(); } if (declarationRoot.getType() == Token.FUNCTION) { // Add the function parameters Node fnParams = declarationRoot.getFirstChild().getNext(); for (Node c = fnParams.getFirstChild(); c != null; c = c.getNext()) { String name = c.getString(); renamer.addDeclaredName(name); } // Add the function body declarations Node functionBody = declarationRoot.getLastChild(); findDeclaredNames(functionBody, null, renamer); } else { // Add the block declarations findDeclaredNames(declarationRoot, null, renamer); } nameStack.push(renamer); } @Override public void exitScope(NodeTraversal t) { if (!t.inGlobalScope()) { nameStack.pop(); } } @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.FUNCTION: { // Add recursive function name, if needed. // NOTE: "enterScope" is called after we need to pick up this name. Renamer renamer = nameStack.peek().forChildScope(); // If needed, add the function recursive name. String name = n.getFirstChild().getString(); if (name != null && !name.isEmpty() && parent != null && !NodeUtil.

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>isFunctionDeclaration(n)) { renamer.addDeclaredName(name); } nameStack.push(renamer); } break; case Token.CATCH: { Renamer renamer = nameStack.peek().forChildScope(); String name = n.getFirstChild().getString(); renamer.addDeclaredName(name); nameStack.push(renamer); } break; } return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.NAME: String newName = getReplacementName(n.getString()); if (newName != null) { Renamer renamer = nameStack.peek(); if (renamer.stripConstIfReplaced()) { // TODO(johnlenz): Do we need to do anything about the javadoc? n.removeProp(Node.IS_CONSTANT_NAME); } n.setString(newName); t.getCompiler().reportCodeChange(); } break; case Token.FUNCTION: // Remove function recursive name (if any). nameStack.pop(); break; case Token.CATCH: // Remove catch except name from the stack of names. nameStack.pop(); break; } } /** * Walks the stack of name maps and finds the replacement name for the * current scope. */ private String getReplacementName(String oldName) { for (Renamer names : nameStack) { String newName = names.getReplacementName(oldName); if (newName != null) { return newName; } } return null; } /** * Traverses the current scope and collects declared names. Does not * decent into functions or add CATCH exceptions. */ private void findDeclaredNames(Node n, Node parent, Renamer renamer) { // Do a shallow traversal, so don't traverse into function declarations, // except for the name of the function itself. if (parent == null || parent.getType() != Token.FUNCTION || n == parent.getFirstChild()) { if (NodeUtil.isVarDeclaration(n)) { renamer.addDeclaredName(n.getString()); }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> /** * Prepare a set for the new scope. */ public void enterScope(NodeTraversal t) { if (t.inGlobalScope()) { return; } referenceStack.push(referencedNames); referencedNames = Sets.newHashSet(); } /** * Rename vars for the current scope, and merge any referenced * names into the parent scope reference set. */ public void exitScope(NodeTraversal t) { if (t.inGlobalScope()) { return; } for (Iterator<Var> it = t.getScope().getVars(); it.hasNext();) { Var v = it.next(); handleScopeVar(v); } // Merge any names that were referenced but not declared in the current // scope. Set<String> current = referencedNames; referencedNames = referenceStack.pop(); // If there isn't anything left in the stack we will be going into the // global scope: don't try to build a set of referenced names for the // global scope. if (!referenceStack.isEmpty()) { referencedNames.addAll(current); } } /** * For the Var declared in the current scope determine if it is possible * to revert the name to its orginal form without conflicting with other * values. */ void handleScopeVar(Var v) { String name = v.getName(); if (containsSeparator(name)) { String newName = getOrginalName(name); // Check if the new name is valid and if it would cause conflicts. if (TokenStream.isJSIdentifier(newName) && !referencedNames.contains(newName) && !newName.equals(ARGUMENTS)) { referencedNames.remove(name); // Adding a reference to the new name to prevent either the parent // scopes or the current scope renaming another var to this new name. referencedNames.add(newName); List<Node> references = nameMap.get(name); Preconditions.checkState(references != null); for (Node n : references) { Preconditions.checkState(n.getType() == Token.NAME); n.setString(newName); } compiler.reportCodeChange(); } nameMap.remove(name); } } @Override public

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>ARGUMENTS)) { if (global) { reserveName(name); } else { // It hasn't been declared locally yet, so increment the count. if (!declarations.containsKey(name)) { int id = incrementNameCount(name); String newName = null; if (id != 0) { newName = getUniqueName(name, id); } declarations.put(name, newName); } } } } @Override public String getReplacementName(String oldName) { return declarations.get(oldName); } /** * Given a name and the associated id, create a new unique name. */ private String getUniqueName(String name, int id) { return name + UNIQUE_ID_SEPARATOR + id; } private void reserveName(String name) { nameUsage.setCount(name, 0, 1); } private int incrementNameCount(String name) { return nameUsage.add(name, 1); } @Override public boolean stripConstIfReplaced() { return false; } } /** * Rename every declared name to be unique. Typically this would be used * when injecting code to insure that names do not conflict with existing * names. * * Used by the FunctionInjector * @see FunctionInjector */ static class InlineRenamer implements Renamer { private final Map<String, String> declarations = Maps.newHashMap(); private final Supplier<String> uniqueIdSupplier; private final String idPrefix; private final boolean removeConstness; InlineRenamer( Supplier<String> uniqueIdSupplier, String idPrefix, boolean removeConstness) { this.uniqueIdSupplier = uniqueIdSupplier; // To ensure that the id does not conflict with the id from the // ContextualRenamer some prefix is needed. Preconditions.checkArgument(!idPrefix.isEmpty()); this.idPrefix = idPrefix; this.removeConstness = removeConstness; } @Override public void addDeclaredName(String name) { Preconditions.checkState(!name.equals(ARGUMENTS)); if (!declarations.containsKey(name)) { declarations.put(name, getUniqueName(name)); } } private String getUniqueName

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> void checkNameDeprecation(NodeTraversal t, Node n, Node parent) { // Don't bother checking definitions or constructors. if (parent.getType() == Token.FUNCTION || parent.getType() == Token.VAR || parent.getType() == Token.NEW) { return; } Scope.Var var = t.getScope().getVar(n.getString()); JSDocInfo docInfo = var == null ? null : var.getJSDocInfo(); if (docInfo != null && docInfo.isDeprecated() && shouldEmitDeprecationWarning(t, n, parent)) { if (docInfo.getDeprecationReason() != null) { compiler.report( JSError.make(t, n, DEPRECATED_NAME_REASON, n.getString(), docInfo.getDeprecationReason())); } else { compiler.report( JSError.make(t, n, DEPRECATED_NAME, n.getString())); } } } /** * Checks the given GETPROP node to ensure that access restrictions are * obeyed. */ private void checkPropertyDeprecation(NodeTraversal t, Node n, Node parent) { // Don't bother checking constructors. if (parent.getType() == Token.NEW) { return; } ObjectType objectType = ObjectType.cast(dereference(n.getFirstChild().getJSType())); String propertyName = n.getLastChild().getString(); if (objectType != null) { String deprecationInfo = getPropertyDeprecationInfo(objectType, propertyName); if (deprecationInfo != null && shouldEmitDeprecationWarning(t, n, parent)) { if (!deprecationInfo.isEmpty()) { compiler.report( JSError.make(t, n, DEPRECATED_PROP_REASON, propertyName, validator.getReadableJSTypeName(n.getFirstChild(), true), deprecationInfo)); } else { compiler.report( JSError.make(t, n, DEPRECATED_PROP, propertyName, validator.getReadableJSTypeName(n.getFirstChild(), true))); } } } } /** * Determines whether the given name is visible in the current context. * @param t The current traversal. * @param name The name node. */ private void checkNameVisibility(

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>getParent(); return // Case #1 (deprecatedDepth > 0) || // Case #2 (getTypeDeprecationInfo(t.getScope().getTypeOfThis()) != null) || // Case #3 (scopeRootParent != null && scopeRootParent.getType() == Token.ASSIGN && getTypeDeprecationInfo( getClassOfMethod(scopeRoot, scopeRootParent)) != null); } /** * Returns whether this is a function node annotated as deprecated. */ private static boolean isDeprecatedFunction(Node n, Node parent) { if (n.getType() == Token.FUNCTION) { JSType type = n.getJSType(); if (type != null) { return getTypeDeprecationInfo(type) != null; } } return false; } /** * Returns the deprecation reason for the type if it is marked * as being deprecated. Returns empty string if the type is deprecated * but no reason was given. Returns null if the type is not deprecated. */ private static String getTypeDeprecationInfo(JSType type) { if (type == null) { return null; } JSDocInfo info = type.getJSDocInfo(); if (info != null && info.isDeprecated()) { if (info.getDeprecationReason() != null) { return info.getDeprecationReason(); } return ""; } ObjectType objType = ObjectType.cast(type); if (objType != null) { ObjectType implicitProto = objType.getImplicitPrototype(); if (implicitProto != null) { return getTypeDeprecationInfo(implicitProto); } } return null; } /** * Returns the deprecation reason for the property if it is marked * as being deprecated. Returns empty string if the property is deprecated * but no reason was given. Returns null if the property is not deprecated. */ private static String getPropertyDeprecationInfo(ObjectType type, String prop) { JSDocInfo info = type.getOwnPropertyJSDocInfo(prop); if (info != null && info.isDeprecated()) { if (info.getDeprecationReason() != null) { return info.getDeprecationReason(); } return ""; } ObjectType implicitProto = type.getImplicitPrototype(); if

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> inlining, so we do this before // the main optimization loop. if (options.collapseProperties) { passes.add(collapseProperties); } // Tighten types based on actual usage. if (options.tightenTypes) { passes.add(tightenTypesBuilder); } // Property disambiguation should only run once and needs to be done // soon after type checking, both so that it can make use of type // information and so that other passes can take advantage of the renamed // properties. if (options.disambiguateProperties) { passes.add(disambiguateProperties); } if (options.computeFunctionSideEffects) { passes.add(markPureFunctions); } else if (options.markNoSideEffectCalls) { // TODO(user) The properties that this pass adds to CALL and NEW // AST nodes increase the AST's in-memory size. Given that we are // already running close to our memory limits, we could run into // trouble if we end up using the @nosideeffects annotation a lot // or compute @nosideeffects annotations by looking at function // bodies. It should be easy to propagate @nosideeffects // annotations as part of passes that depend on this property and // store the result outside the AST (which would allow garbage // collection once the pass is done). passes.add(markNoSideEffectCalls); } if (options.chainCalls) { passes.add(chainCalls); } // Constant checking must be done after property collapsing because // property collapsing can introduce new constants (e.g. enum values). if (options.inlineConstantVars) { passes.add(checkConsts); } // The Caja library adds properties to Object.prototype, which breaks // most for-in loops. This adds a check to each loop that skips // any property matching /___$/. if (options.ignoreCajaProperties) { passes.add(ignoreCajaProperties); } assertAllOneTimePasses(passes); if (options.smartNameRemoval || options.reportPath != null) { passes.addAll(getCodeRemovingPasses()); passes.add(smartNamePass); } // TODO(user): This forces a

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> first crack at crossModuleCodeMotion // before devirtualization. Once certain functions are devirtualized, // it confuses crossModuleCodeMotion ability to recognized that // it is recursive. // TODO(user): This is meant for a temporary quick win. // In the future, we might want to improve our analysis in // CrossModuleCodeMotion so we don't need to do this. if (options.crossModuleCodeMotion) { passes.add(crossModuleCodeMotion); } // Method devirtualization benefits from property disambiguiation so // it should run after that pass but before passes that do // optimizations based on global names (like cross module code motion // and inline functions). Smart Name Removal does better if run before // this pass. if (options.devirtualizePrototypeMethods) { passes.add(devirtualizePrototypeMethods); } if (options.customPasses != null) { passes.add(getCustomPasses( CustomPassExecutionTime.BEFORE_OPTIMIZATION_LOOP)); } passes.add(createEmptyPass("beforeMainOptimizations")); passes.addAll(getMainOptimizationLoop()); passes.add(createEmptyPass("beforeModuleMotion")); if (options.crossModuleCodeMotion) { passes.add(crossModuleCodeMotion); } if (options.crossModuleMethodMotion) { passes.add(crossModuleMethodMotion); } passes.add(createEmptyPass("afterModuleMotion")); // Some optimizations belong outside the loop because running them more // than once would either have no benefit or be incorrect. if (options.customPasses != null) { passes.add(getCustomPasses( CustomPassExecutionTime.AFTER_OPTIMIZATION_LOOP)); } if (options.flowSensitiveInlineVariables) { passes.add(flowSensitiveInlineVariables); // After inlining some of the variable uses, some variables are unused. // Re-run remove unused vars to clean it up. if (options.removeUnusedVars) { passes.add(removeUnusedVars); } } if (options.collapseAnonymousFunctions) { passes.add(collapseAnonymousFunctions); } // Move functions before extracting prototype member declarations. if (options.moveFunctionDeclarations) { passes

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> } passes.add(denormalize); if (options.instrumentationTemplate != null) { passes.add(instrumentFunctions); } if (options.variableRenaming != VariableRenamingPolicy.ALL) { // If we're leaving some (or all) variables with their old names, // then we need to undo any of the markers we added for distinguishing // local variables ("$$1"). passes.add(invertContextualRenaming); } if (options.variableRenaming != VariableRenamingPolicy.OFF) { passes.add(renameVars); } // This pass should run after names stop changing. if (options.processObjectPropertyString) { passes.add(objectPropertyStringPostprocess); } if (options.labelRenaming) { passes.add(renameLabels); } if (options.anonymousFunctionNaming == AnonymousFunctionNamingPolicy.UNMAPPED) { passes.add(nameUnmappedAnonymousFunctions); } // Safety check if (options.checkSymbols) { passes.add(sanityCheckVars); } return passes; } /** Creates the passes for the main optimization loop. */ private List<PassFactory> getMainOptimizationLoop() { List<PassFactory> passes = Lists.newArrayList(); if (options.inlineGetters) { passes.add(inlineGetters); } passes.addAll(getCodeRemovingPasses()); if (options.inlineFunctions || options.inlineLocalFunctions) { passes.add(inlineFunctions); } if (options.removeUnusedVars) { if (options.deadAssignmentElimination) { passes.add(deadAssignmentsElimination); } passes.add(removeUnusedVars); } assertAllLoopablePasses(passes); return passes; } /** Creates several passes aimed at removing code. */ private List<PassFactory> getCodeRemovingPasses() { List<PassFactory> passes = Lists.newArrayList(); if (options.inlineVariables || options.inlineLocalVariables) { passes.add(inlineVariables); } else if (options.inlineConstantVars) { passes.add(inlineConstants); } if (options.removeConstantExpressions) { passes.add(removeConstantExpressions); } if

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> (options.foldConstants) { // These used to be one pass. passes.add(minimizeExitPoints); passes.add(foldConstants); } if (options.removeDeadCode) { passes.add(removeUnreachableCode); } if (options.removeUnusedPrototypeProperties) { passes.add(removeUnusedPrototypeProperties); } assertAllLoopablePasses(passes); return passes; } /** * Checks for code that is probably wrong (such as stray expressions). */ // TODO(bolinfest): Write a CompilerPass for this. final PassFactory suspiciousCode = new PassFactory("suspiciousCode", true) { @Override protected CompilerPass createInternal(final AbstractCompiler compiler) { List<Callback> sharedCallbacks = Lists.newArrayList(); sharedCallbacks.add(new CheckAccidentalSemicolon(CheckLevel.WARNING)); sharedCallbacks.add(new CheckSideEffects(CheckLevel.WARNING)); if (options.checkGlobalThisLevel.isOn()) { sharedCallbacks.add( new CheckGlobalThis(compiler, options.checkGlobalThisLevel)); } return combineChecks(compiler, sharedCallbacks); } }; /** Verify that all the passes are one-time passes. */ private void assertAllOneTimePasses(List<PassFactory> passes) { for (PassFactory pass : passes) { Preconditions.checkState(pass.isOneTimePass()); } } /** Verify that all the passes are multi-run passes. */ private void assertAllLoopablePasses(List<PassFactory> passes) { for (PassFactory pass : passes) { Preconditions.checkState(!pass.isOneTimePass()); } } /** Checks for validity of the control structures. */ private final PassFactory checkControlStructures = new PassFactory("checkControlStructures", true) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { return new ControlStructureCheck(compiler); } }; /** Checks that all constructed classes are goog.require()d. */ private final PassFactory checkRequires = new PassFactory("checkRequires", true) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { return new CheckRequiresForConstructors(compiler, options.checkRequires

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>); } }; /** Makes sure @constructor is paired with goog.provides(). */ private final PassFactory checkProvides = new PassFactory("checkProvides", true) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { return new CheckProvides(compiler, options.checkProvides); } }; private static final DiagnosticType GENERATE_EXPORTS_ERROR = DiagnosticType.error( "JSC_GENERATE_EXPORTS_ERROR", "Exports can only be generated if export symbol/property " + "functions are set."); /** Generates exports for @export annotations. */ private final PassFactory generateExports = new PassFactory("generateExports", true) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { CodingConvention convention = compiler.getCodingConvention(); if (convention.getExportSymbolFunction() != null && convention.getExportPropertyFunction() != null) { return new GenerateExports(compiler, convention.getExportSymbolFunction(), convention.getExportPropertyFunction()); } else { return new ErrorPass(compiler, GENERATE_EXPORTS_ERROR); } } }; /** Generates exports for functions associated with JSUnit. */ private final PassFactory exportTestFunctions = new PassFactory("exportTestFunctions", true) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { CodingConvention convention = compiler.getCodingConvention(); if (convention.getExportSymbolFunction() != null) { return new ExportTestFunctions(compiler, convention.getExportSymbolFunction()); } else { return new ErrorPass(compiler, GENERATE_EXPORTS_ERROR); } } }; /** Raw exports processing pass. */ final PassFactory gatherRawExports = new PassFactory("gatherRawExports", false) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { final GatherRawExports pass = new GatherRawExports( compiler); return new CompilerPass() { @Override public void process(Node externs, Node root) { pass.process(externs, root); if (exportedNames == null) { exportedNames = Sets.newHashSet(); } exportedNames.addAll(pass.getExportedVariableNames()); } }; } }; /** Closure pre

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>-processing pass. */ @SuppressWarnings("deprecation") final PassFactory closurePrimitives = new PassFactory("processProvidesAndRequires", false) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { final ProcessClosurePrimitives pass = new ProcessClosurePrimitives( compiler, options.brokenClosureRequiresLevel, options.rewriteNewDateGoogNow); return new CompilerPass() { @Override public void process(Node externs, Node root) { pass.process(externs, root); exportedNames = pass.getExportedVariableNames(); } }; } }; /** Checks that CSS class names are wrapped in goog.getCssName */ private final PassFactory closureCheckGetCssName = new PassFactory("checkMissingGetCssName", true) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { String blacklist = options.checkMissingGetCssNameBlacklist; Preconditions.checkState(blacklist != null && !blacklist.isEmpty(), "Not checking use of goog.getCssName because of empty blacklist."); return new CheckMissingGetCssName( compiler, options.checkMissingGetCssNameLevel, blacklist); } }; /** * Processes goog.getCssName. The cssRenamingMap is used to lookup * replacement values for the classnames. If null, the raw class names are * inlined. */ private final PassFactory closureReplaceGetCssName = new PassFactory("renameCssNames", true) { @Override protected CompilerPass createInternal(final AbstractCompiler compiler) { return new CompilerPass() { @Override public void process(Node externs, Node jsRoot) { Map<String, Integer> newCssNames = null; if (options.gatherCssNames) { newCssNames = Maps.newHashMap(); } (new ReplaceCssNames(compiler, newCssNames)).process( externs, jsRoot); cssNames = newCssNames; } }; } }; /** * Creates synthetic blocks to prevent FoldConstants from moving code * past markers in the source. */ private final PassFactory createSyntheticBlocks = new PassFactory("createSyntheticBlocks", true) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { return new CreateSynt

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>(AbstractCompiler compiler) { return new GlobalTypeResolver(compiler); } }; /** Rusn type inference. */ private final PassFactory inferTypes = new PassFactory("inferTypes", false) { @Override protected CompilerPass createInternal(final AbstractCompiler compiler) { return new CompilerPass() { @Override public void process(Node externs, Node root) { Preconditions.checkNotNull(topScope); Preconditions.checkNotNull(typedScopeCreator); makeTypeInference(compiler).process(externs, root); } }; } }; /** Checks type usage */ private final PassFactory checkTypes = new PassFactory("checkTypes", false) { @Override protected CompilerPass createInternal(final AbstractCompiler compiler) { return new CompilerPass() { @Override public void process(Node externs, Node root) { Preconditions.checkNotNull(topScope); Preconditions.checkNotNull(typedScopeCreator); TypeCheck check = makeTypeCheck(compiler); check.process(externs, root); compiler.getErrorManager().setTypedPercent(check.getTypedPercent()); } }; } }; /** * Checks possible execution paths of the program for problems: missing return * statements and dead code. */ private final PassFactory checkControlFlow = new PassFactory("checkControlFlow", true) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { List<Callback> callbacks = Lists.newArrayList(); if (options.checkUnreachableCode.isOn()) { callbacks.add( new CheckUnreachableCode(compiler, options.checkUnreachableCode)); } if (options.checkMissingReturn.isOn() && options.checkTypes) { callbacks.add( new CheckMissingReturn(compiler, options.checkMissingReturn)); } return combineChecks(compiler, callbacks); } }; /** Checks access controls. Depends on type-inference. */ private final PassFactory checkAccessControls = new PassFactory("checkAccessControls", true) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { return new CheckAccessControls(compiler); } }; /** Executes the given callbacks with a {@link CombinedCompilerPass}. */ private static CompilerPass combineChecks(AbstractCompiler compiler, List<Callback

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>> callbacks) { Preconditions.checkArgument(callbacks.size() > 0); Callback[] array = callbacks.toArray(new Callback[callbacks.size()]); return new CombinedCompilerPass(compiler, array); } /** A compiler pass that resolves types in the global scope. */ private class GlobalTypeResolver implements CompilerPass { private final AbstractCompiler compiler; GlobalTypeResolver(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node root) { if (topScope == null) { typedScopeCreator = new MemoizedScopeCreator(new TypedScopeCreator(compiler)); topScope = typedScopeCreator.createScope(root.getParent(), null); } else { compiler.getTypeRegistry().resolveTypesInScope(topScope); } } } /** Checks global name usage. */ private final PassFactory checkGlobalNames = new PassFactory("Check names", true) { @Override protected CompilerPass createInternal(final AbstractCompiler compiler) { return new CompilerPass() { @Override public void process(Node externs, Node jsRoot) { // Create a global namespace for analysis by check passes. // Note that this class does all heavy computation lazily, // so it's OK to create it here. namespaceForChecks = new GlobalNamespace(compiler, jsRoot); new CheckGlobalNames(compiler, options.checkGlobalNamesLevel) .injectNamespace(namespaceForChecks).process(externs, jsRoot); } }; } }; /** Checks for properties that are not read or written */ private final PassFactory checkSuspiciousProperties = new PassFactory("checkSuspiciousProperties", true) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { return new SuspiciousPropertiesCheck( compiler, options.checkUndefinedProperties, options.checkUnusedPropertiesEarly ? CheckLevel.WARNING : CheckLevel.OFF); } }; /** Checks that the code is ES5 or Caja compliant. */ private final PassFactory checkStrictMode = new PassFactory("checkStrictMode", true) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { return new StrictModeCheck(compiler, !options.checkSymbols, // don't check

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> * <ul> * <li>{@code (number,string)} restricted by {@code number} is * {@code string}</li> * <li>{@code (null, EvalError, URIError)} restricted by * {@code Error} is {@code null}</li> * </ul> * * @param type the supertype of the types to remove from this union type */ public JSType getRestrictedUnion(JSType type) { UnionTypeBuilder restricted = new UnionTypeBuilder(registry); for (JSType t : alternates) { if (t.isUnknownType() || !t.isSubtype(type)) { restricted.addAlternate(t); } } return restricted.build(); } @Override public String toString() { StringBuilder result = new StringBuilder(); boolean firstAlternate = true; result.append("("); SortedSet<JSType> sorted = new TreeSet<JSType>(ALPHA); sorted.addAll(alternates); for (JSType t : sorted) { if (!firstAlternate) { result.append("|"); } result.append(t.toString()); firstAlternate = false; } result.append(")"); return result.toString(); } @Override public boolean isSubtype(JSType that) { for (JSType element : alternates) { if (!element.isSubtype(that)) { return false; } } return true; } @Override public JSType getRestrictedTypeGivenToBooleanOutcome(boolean outcome) { // gather elements after restriction UnionTypeBuilder restricted = new UnionTypeBuilder(registry); for (JSType element : alternates) { restricted.addAlternate( element.getRestrictedTypeGivenToBooleanOutcome(outcome)); } return restricted.build(); } @Override public BooleanLiteralSet getPossibleToBooleanOutcomes() { BooleanLiteralSet literals = BooleanLiteralSet.EMPTY; for (JSType element : alternates) { literals = literals.union(element.getPossibleToBooleanOutcomes()); if (literals == BooleanLiteralSet.BOTH) { break; } } return literals; } @Override public TypePair getTypesUnderEquality(JSType that) { UnionTypeBuilder thisRestricted = new

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> { JSType newAlternate = alternate.resolve(t, scope); changed |= (alternate != newAlternate); resolvedTypes.add(alternate); } if (changed) { Set<JSType> newAlternates = resolvedTypes.build(); Preconditions.checkState(newAlternates.hashCode() == this.hashcode); alternates = newAlternates; } return this; } }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>; case JAVA_DISPATCH: if (!jsdocBuilder.recordJavaDispatch()) { parser.addWarning("msg.jsdoc.javadispatch", stream.getLineno(), stream.getCharno()); } token = eatTokensUntilEOL(); continue retry; case EXTENDS: case IMPLEMENTS: skipEOLs(); token = next(); lineno = stream.getLineno(); charno = stream.getCharno(); boolean matchingRc = false; if (token == JsDocToken.LC) { token = next(); matchingRc = true; } if (token == JsDocToken.STRING) { Node typeNode = parseAndRecordTypeNameNode( token, lineno, charno, matchingRc); lineno = stream.getLineno(); charno = stream.getCharno(); typeNode = wrapNode(Token.BANG, typeNode); if (typeNode != null && !matchingRc) { typeNode.putBooleanProp(Node.BRACELESS_TYPE, true); } type = createJSTypeExpression(typeNode); if (annotation == Annotation.EXTENDS) { if (!jsdocBuilder.recordBaseType(type)) { parser.addWarning( "msg.jsdoc.incompat.type", lineno, charno); } } else { Preconditions.checkState( annotation == Annotation.IMPLEMENTS); if (!jsdocBuilder.recordImplementedInterface(type)) { parser.addWarning("msg.jsdoc.implements.duplicate", lineno, charno); } } token = next(); if (matchingRc) { if (token != JsDocToken.RC) { parser.addWarning("msg.jsdoc.missing.rc", stream.getLineno(), stream.getCharno()); } } else if (token != JsDocToken.EOL && token != JsDocToken.EOF && token != JsDocToken.EOC) { parser.addWarning("msg.end.annotation.expected", stream.getLineno(), stream.getCharno()); } } else { parser.addWarning("msg.no.type.name", lineno, charno); } token = eatTokensUntilEOL(token); continue retry; case

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> found or null if none. */ private Node parseAndRecordParamTypeNode(JsDocToken token) { Preconditions.checkArgument(token == JsDocToken.LC); int lineno = stream.getLineno(); int startCharno = stream.getCharno(); Node typeNode = parseParamTypeExpressionAnnotation(token); int endCharno = stream.getCharno(); jsdocBuilder.markTypeNode(typeNode, lineno, startCharno, endCharno, true); return typeNode; } /** * Looks for a parameter type expression at the current token and if found, * returns it. Note that this method consumes input. * * @param token The current token. * @param lineno The line of the type expression. * @param startCharno The starting character position of the type expression. * @param matchingLC Whether the type expression starts with a "{". * @param onlyParseSimpleNames If true, only simple type names are parsed * (via a call to parseTypeNameAnnotation instead of * parseTypeExpressionAnnotation). * @return The type expression found or null if none. */ private Node parseAndRecordTypeNode(JsDocToken token, int lineno, int startCharno, boolean matchingLC, boolean onlyParseSimpleNames) { Node typeNode = null; if (onlyParseSimpleNames) { typeNode = parseTypeNameAnnotation(token); } else { typeNode = parseTypeExpressionAnnotation(token); } if (typeNode != null && !matchingLC) { typeNode.putBooleanProp(Node.BRACELESS_TYPE, true); } int endCharno = stream.getCharno(); jsdocBuilder.markTypeNode(typeNode, lineno, startCharno, endCharno, matchingLC); return typeNode; } /** * Determines whether the given type is a valid {@code @define} type. */ // TODO(nicksantos): Move this into a check pass. private boolean isValidDefineType(Node typeNode) { JSType type = typeRegistry.createFromTypeNodes(typeNode, "", null); return !type.isUnknownType() && type.isSubtype( typeRegistry.getNativeType(JSTypeNative.NUMBER_STRING_

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> = parseTopLevelTypeExpression(next()); if (typeNode != null) { skipEOLs(); if (!match(JsDocToken.RC)) { reportTypeSyntaxWarning("msg.jsdoc.missing.rc"); } else { next(); } } return typeNode; } else { return parseTypeExpression(token); } } /** * ParamTypeExpressionAnnotation := * '{' OptionalParameterType '}' | * '{' TopLevelTypeExpression '}' | * '{' '...' TopLevelTypeExpression '}' * * OptionalParameterType := * TopLevelTypeExpression '=' */ private Node parseParamTypeExpressionAnnotation(JsDocToken token) { Preconditions.checkArgument(token == JsDocToken.LC); skipEOLs(); boolean restArg = false; token = next(); if (token == JsDocToken.ELLIPSIS) { token = next(); if (token == JsDocToken.RC) { // EMPTY represents the UNKNOWN type in the Type AST. return wrapNode(Token.ELLIPSIS, new Node(Token.EMPTY)); } restArg = true; } Node typeNode = parseTopLevelTypeExpression(token); if (typeNode != null) { skipEOLs(); if (restArg) { typeNode = wrapNode(Token.ELLIPSIS, typeNode); } else if (match(JsDocToken.EQUALS)) { next(); skipEOLs(); typeNode = wrapNode(Token.EQUALS, typeNode); } if (!match(JsDocToken.RC)) { reportTypeSyntaxWarning("msg.jsdoc.missing.rc"); } else { next(); } } return typeNode; } /** * TypeNameAnnotation := TypeName | '{' TypeName '}' */ private Node parseTypeNameAnnotation(JsDocToken token) { if (token == JsDocToken.LC) { skipEOLs(); Node typeNode = parseTypeName(next()); if (typeNode != null) { skipEOLs(); if (!match(JsDocToken.RC)) { reportTypeSyntaxWarning("msg.jsdoc.missing.rc"); } else { next(); } } return

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>Name, ParametersType ')' ResultType */ private Node parseFunctionType(JsDocToken token) { // NOTE(nicksantos): We're not implementing generics at the moment, so // just throw out TypeParameters. if (token != JsDocToken.LP) { return reportTypeSyntaxWarning("msg.jsdoc.missing.lp"); } Node functionType = newNode(Token.FUNCTION); Node parameters = null; skipEOLs(); if (!match(JsDocToken.RP)) { token = next(); boolean hasParams = true; if (token == JsDocToken.STRING && "this".equals(stream.getString())) { if (match(JsDocToken.COLON)) { next(); skipEOLs(); Node thisType = wrapNode(Token.THIS, parseTypeName(next())); if (thisType == null) { return null; } functionType.addChildToFront(thisType); } else { return reportTypeSyntaxWarning("msg.jsdoc.missing.colon"); } if (match(JsDocToken.COMMA)) { next(); skipEOLs(); token = next(); } else { hasParams = false; } } if (hasParams) { parameters = parseParametersType(token); if (parameters == null) { return null; } } } if (parameters != null) { functionType.addChildToBack(parameters); } skipEOLs(); if (!match(JsDocToken.RP)) { return reportTypeSyntaxWarning("msg.jsdoc.missing.rp"); } skipEOLs(); Node resultType = parseResultType(next()); if (resultType == null) { return null; } else { functionType.addChildToBack(resultType); } return functionType; } /** * ParametersType := RestParameterType | NonRestParametersType * | NonRestParametersType ',' RestParameterType * RestParameterType := '...' Identifier * NonRestParametersType := ParameterType ',' NonRestParametersType * | ParameterType * | OptionalParametersType * OptionalParametersType := OptionalParameterType * | OptionalParameterType, OptionalParametersType * OptionalParameterType := ParameterType= * ParameterType := TypeExpression | Identifier ':'

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> } paramsType.addChildToBack(paramType); if (isVarArgs) { break; } } while (match(JsDocToken.COMMA)); } if (isVarArgs && match(JsDocToken.COMMA)) { return reportTypeSyntaxWarning("msg.jsdoc.function.varargs"); } // The right paren will be checked by parseFunctionType return paramsType; } /** * ResultType := <empty> | ':' void | ':' TypeExpression */ private Node parseResultType(JsDocToken token) { skipEOLs(); if (!match(JsDocToken.COLON)) { return newNode(Token.EMPTY); } token = next(); skipEOLs(); if (match(JsDocToken.STRING) && "void".equals(stream.getString())) { next(); return newNode(Token.VOID); } else { return parseTypeExpression(next()); } } /** * UnionType := '(' TypeUnionList ')' * TypeUnionList := TypeExpression | TypeExpression '|' TypeUnionList * * We've removed the empty union type. */ private Node parseUnionType(JsDocToken token) { return parseUnionTypeWithAlternate(token, null); } /** * Create a new union type, with an alternate that has already been * parsed. The alternate may be null. */ private Node parseUnionTypeWithAlternate(JsDocToken token, Node alternate) { Node union = newNode(Token.PIPE); if (alternate != null) { union.addChildToBack(alternate); } Node expr = null; do { if (expr != null) { skipEOLs(); token = next(); Preconditions.checkState( token == JsDocToken.PIPE || token == JsDocToken.COMMA); boolean isPipe = token == JsDocToken.PIPE; if (isPipe && match(JsDocToken.PIPE)) { // We support double pipes for backwards compatiblity. next(); } skipEOLs(); token = next(); } expr = parseTypeExpression(token); if (expr == null) { return null; } union.addChildToBack(expr); // We support commas for backwards compatiblity.

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> public void process(Node externs, Node root) { SimpleDefinitionFinder defFinder = new SimpleDefinitionFinder(compiler); defFinder.process(externs, root); // Gather the list of function nodes that have @nosideeffect annotations. // For use by SetNoSideEffectCallProperty. NodeTraversal.traverse( compiler, externs, new GatherNoSideEffectFunctions(true)); NodeTraversal.traverse( compiler, root, new GatherNoSideEffectFunctions(false)); NodeTraversal.traverse(compiler, root, new SetNoSideEffectCallProperty(defFinder)); } /** * Determines if the type of the value of the rhs expression can * be a function node. */ private static boolean definitionTypeContainsFunctionType(Definition def) { Node rhs = def.getRValue(); if (rhs == null) { return true; } switch (rhs.getType()) { case Token.ASSIGN: case Token.AND: case Token.CALL: case Token.GETPROP: case Token.GETELEM: case Token.FUNCTION: case Token.HOOK: case Token.NAME: case Token.NEW: case Token.OR: return true; default: return false; } } /** * Get the value of the @nosideeffects annotation stored in the * doc info. */ private static boolean hasNoSideEffectsAnnotation(Node node) { JSDocInfo docInfo = node.getJSDocInfo(); return docInfo != null && docInfo.isNoSideEffects(); } /** * Gather function nodes that have @nosideeffects annotations. */ private class GatherNoSideEffectFunctions extends AbstractPostOrderCallback { private final boolean inExterns; GatherNoSideEffectFunctions(boolean inExterns) { this.inExterns = inExterns; } @Override public void visit(NodeTraversal traversal, Node node, Node parent) { if (!inExterns && hasNoSideEffectsAnnotation(node)) { traversal.report(node, INVALID_NO_SIDE_EFFECT_ANNOTATION); } if (NodeUtil.isGetProp(node)) { if (NodeUtil.isExpressionNode(parent) && hasNo

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>SideEffectsAnnotation(node)) { noSideEffectFunctionNames.add(node); } } else if (NodeUtil.isFunction(node)) { // The annotation may attached to the function node, the // variable declaration or assignment expression. boolean hasAnnotation = hasNoSideEffectsAnnotation(node); List<Node> nameNodes = Lists.newArrayList(); nameNodes.add(node.getFirstChild()); Node nameNode = null; if (NodeUtil.isName(parent)) { Node gramp = parent.getParent(); if (NodeUtil.isVar(gramp) && gramp.hasOneChild() && hasNoSideEffectsAnnotation(gramp)) { hasAnnotation = true; } nameNodes.add(parent); } else if (NodeUtil.isAssign(parent)) { if (hasNoSideEffectsAnnotation(parent)) { hasAnnotation = true; } nameNodes.add(parent.getFirstChild()); } if (hasAnnotation) { noSideEffectFunctionNames.addAll(nameNodes); } } } } /** * Set the no side effects property for CALL and NEW nodes that * refer to function names that are known to have no side effects. */ private class SetNoSideEffectCallProperty extends AbstractPostOrderCallback { private final SimpleDefinitionFinder defFinder; SetNoSideEffectCallProperty(SimpleDefinitionFinder defFinder) { this.defFinder = defFinder; } @Override public void visit(NodeTraversal traversal, Node node, Node parent) { if (!NodeUtil.isCall(node) && !NodeUtil.isNew(node)) { return; } Collection<Definition> definitions = defFinder.getDefinitionsReferencedAt(node.getFirstChild()); if (definitions == null) { return; } for (Definition def : definitions) { Node lValue = def.getLValue(); Preconditions.checkNotNull(lValue); if (!noSideEffectFunctionNames.contains(lValue) && definitionTypeContainsFunctionType(def)) { return; } } node.setIsNoSideEffectsCall(); } } }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>(), traversal.inGlobalScope(), inExterns)); } } } } private class UseSiteGatheringCallback extends AbstractPostOrderCallback { @Override public void visit(NodeTraversal traversal, Node node, Node parent) { Collection<Definition> defs = getDefinitionsReferencedAt(node); if (defs == null) { return; } Definition first = defs.iterator().next(); String name = getSimplifiedName(first.getLValue()); Preconditions.checkNotNull(name); nameUseSiteMultimap.put( name, new UseSite(node, traversal.getModule())); } } }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> values in the graph. */ public abstract List<GraphEdge<N, E>> getEdges(N n1, N n2); /** * Checks whether the node exists in the graph ({@link #createNode(Object)} * has been called with that value). * * @param n Node. * @return <code>true</code> if it exist. */ public final boolean hasNode(N n) { return getNode(n) != null; } /** * Checks whether two nodes in the graph are connected. * * @param n1 Node 1. * @param n2 Node 2. * @return <code>true</code> if the two nodes are connected. */ public abstract boolean isConnected(N n1, N n2); public final void clearNodeAnnotations() { for (GraphNode<N, E> n : getNodes()) { n.setAnnotation(null); } } /** Makes each edge's annotation null. */ public final void clearEdgeAnnotations() { for (GraphEdge<N, E> e : getEdges()) { e.setAnnotation(null); } } /** * Pushes nodes' annotation values. Restored with * {@link #popNodeAnnotations()}. Nodes' annotation values are cleared. */ public final void pushNodeAnnotations() { if (nodeAnnotationStack == null) { nodeAnnotationStack = Lists.newLinkedList(); } pushAnnotations(nodeAnnotationStack, getNodes()); } /** * Restores nodes' annotation values to state before last * {@link #pushNodeAnnotations()}. */ public final void popNodeAnnotations() { Preconditions.checkNotNull(nodeAnnotationStack, "Popping node annotations without pushing."); popAnnotations(nodeAnnotationStack); } /** * Pushes edges' annotation values. Restored with * {@link #popEdgeAnnotations()}. Edges' annotation values are cleared. */ public final void pushEdgeAnnotations() { if (edgeAnnotationStack == null) { edgeAnnotationStack = Lists.newLinkedList(); } pushAnnotations(edgeAnnotationStack, getEdges()); } /** * Restores edges' annotation values to state before last * {@link #pushEdgeAnnotations()}. */ public final void popEdgeAnnotations

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>() { Preconditions.checkNotNull(edgeAnnotationStack, "Popping edge annotations without pushing."); popAnnotations(edgeAnnotationStack); } /** * A generic edge. * * @param <N> Value type that the graph node stores. * @param <E> Value type that the graph edge stores. */ public interface GraphEdge<N, E> extends Annotatable { /** * Retrieves the edge's value. * * @return The value. */ E getValue(); GraphNode<N, E> getNodeA(); GraphNode<N, E> getNodeB(); } /** * A simple implementation of SubGraph that calculates adjacency by iterating * over a node's neighbors. */ class SimpleSubGraph<N, E> implements SubGraph<N, E> { private Graph<N, E> graph; private List<GraphNode<N, E>> nodes = Lists.newArrayList(); SimpleSubGraph(Graph<N, E> graph) { this.graph = graph; } public boolean isIndependentOf(N value) { GraphNode<N, E> node = graph.getNode(value); for (GraphNode<N, E> n : nodes) { if (graph.getNeighborNodes(n.getValue()).contains(node)) { return false; } } return true; } public void addNode(N value) { if (!graph.hasNode(value)) { throw new IllegalArgumentException(value + " does not exist in graph"); } nodes.add(graph.getNode(value)); } } /** * Pushes a new list on stack and stores nodes annotations in the new list. * Clears objects' annotations as well. */ private static void pushAnnotations( Deque<GraphAnnotationState> stack, Collection<? extends Annotatable> haveAnnotations) { stack.push(new GraphAnnotationState(haveAnnotations.size())); for (Annotatable h : haveAnnotations) { stack.peek().add(new AnnotationState(h, h.getAnnotation())); h.setAnnotation(null); } } /** * Restores the node annotations on the top of stack and pops stack. */ private static void popAnnotations(Deque<GraphAnnotationState> stack) {

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>/* * Copyright 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.base.Supplier; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.util.Set; /** * Methods necessary for partially or full decomposing an expression. Initially * this is intended to expanded the locations were inlining can occur, but has * other uses as well. * * For example: * var x = y() + z(); * * Becomes: * var a = y(); * var b = z(); * x = a + b; * * @author johnlenz@google.com (John Lenz) */ class ExpressionDecomposer { /** * @see #canExposeExpression */ enum DecompositionType { UNDECOMPOSABLE, MOVABLE, DECOMPOSABLE } private final AbstractCompiler compiler; private final Supplier<String> safeNameIdSupplier; private final Set<String> knownConstants; public ExpressionDecomposer( AbstractCompiler compiler, Supplier<String> safeNameIdSupplier, Set<String> constNames) { Preconditions.checkNotNull(compiler); Preconditions.checkNotNull(safeNameIdSupplier); Preconditions.checkNotNull(constNames); this.compiler = compiler; this.safeNameIdSupplier = safeNameIdSupplier; this.knownConstants = constNames; } // An arbitrary limit to prevent catch infinite

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> recursion. private static final int MAX_INTERATIONS = 100; /** * If required, rewrite the statement containing the expression. * @param expression The expression to be exposed. * @see #canExposeExpression */ void maybeDecomposeExpression(Node expression) { // If the expression needs to exposed. int i = 0; while (DecompositionType.DECOMPOSABLE == canExposeExpression(expression)) { exposeExpression(expression); if (i > MAX_INTERATIONS) { throw new IllegalStateException( "DecomposeExpression depth exceeded on :\n" + expression.toStringTree()); } } } /** * Perform any rewriting necessary so that the specified expression * is movable. This is a partial expression decomposition. * @see #canExposeExpression */ void exposeExpression(Node expression) { Node expressionRoot = findExpressionRoot(expression); Preconditions.checkState(expressionRoot != null); exposeExpression(expressionRoot, expression); compiler.reportCodeChange(); } // TODO(johnlenz): This is not currently used by the function inliner, // as moving the call out of the expression before the actual function // results in additional variables being introduced. As the variable // inliner is improved, this might be a viable option. /** * Extract the specified expression from its parent expression. * @see #canExposeExpression */ void moveExpression(Node expression) { String resultName = getTempValueName(); // Should this be constant? Node injectionPoint = findInjectionPoint(expression); Preconditions.checkNotNull(injectionPoint); Node injectionPointParent = injectionPoint.getParent(); Preconditions.checkNotNull(injectionPointParent); Preconditions.checkState(NodeUtil.isStatementBlock(injectionPointParent)); // Replace the expression with a reference to the new name. Node expressionParent = expression.getParent(); expressionParent.replaceChild( expression, Node.newString(Token.NAME, resultName)); // Re-add the expression at the appropriate place. Node newExpressionRoot = NodeUtil.newVarNode(resultName, expression); injectionPointParent.addChildBefore(newExpressionRoot, injectionPoint); compiler.reportCodeChange(); } /** * Rewrite the expression such that the sub-expression is

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> in a movable * expression statement while maintaining evaluation order. * * Two types of subexpressions are extracted from the source expression: * 1) subexpressions with side-effects. * 2) conditional expressions, that contain the call, which are transformed * into IF statements. * * The following terms are used: * expressionRoot: The top level node before which the any extracted * expressions should be placed before. * nonconditionalExpr: The node that will be extracted either expres. * */ private void exposeExpression(Node expressionRoot, Node subExpression) { Node nonconditionalExpr = findNonconditionalParent( subExpression, expressionRoot); // Before extraction, record whether there are side-effect boolean hasFollowingSideEffects = NodeUtil.mayHaveSideEffects( nonconditionalExpr); Node exprInjectionPoint = findInjectionPoint(nonconditionalExpr); DecompositionState state = new DecompositionState(); state.sideEffects = hasFollowingSideEffects; state.extractBeforeStatement = exprInjectionPoint; // Extract expressions in the reverse order of their evaluation. for (Node child = nonconditionalExpr, parent = child.getParent(); parent != expressionRoot; child = parent, parent = child.getParent()) { int parentType = parent.getType(); Preconditions.checkState( !isConditionalOp(parent) || child == parent.getFirstChild()); if (parentType == Token.ASSIGN) { if (isSafeAssign(parent, state.sideEffects)) { // It is always safe to inline "foo()" for expressions such as // "a = b = c = foo();" // As the assignment is unaffected by side effect of "foo()" // and the names assigned-to can not influence the state before // the call to foo. // // This is not true of more complex LHS values, such as // a.x = foo(); // next().x = foo(); // in these cases the checks below are necessary. } else { // Alias "next()" in "next().foo" Node left = parent.getFirstChild(); int type = left.getType(); if (left != child) { Preconditions.checkState(NodeUtil.isGet(left)); if (type == Token.GETELEM) { decompose

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>Point, boolean needResult) { Node parent = expr.getParent(); String tempName = getTempValueName(); // Break down the conditional. Node first = expr.getFirstChild(); Node second = first.getNext(); Node last = expr.getLastChild(); // Isolate the children nodes. expr.detachChildren(); // Transform the conditional to an IF statement. Node cond = null; Node trueExpr = new Node(Token.BLOCK); Node falseExpr = new Node(Token.BLOCK); switch (expr.getType()) { case Token.HOOK: // a = x?y:z --> if (x) {a=y} else {a=z} cond = first; trueExpr.addChildToFront(new Node(Token.EXPR_RESULT, buildResultExpression(second, needResult, tempName))); falseExpr.addChildToFront(new Node(Token.EXPR_RESULT, buildResultExpression(last, needResult, tempName))); break; case Token.AND: // a = x&&y --> if (a=x) {a=y} else {} cond = buildResultExpression(first, needResult, tempName); trueExpr.addChildToFront(new Node(Token.EXPR_RESULT, buildResultExpression(last, needResult, tempName))); break; case Token.OR: // a = x||y --> if (a=x) {} else {a=y} cond = buildResultExpression(first, needResult, tempName); falseExpr.addChildToFront(new Node(Token.EXPR_RESULT, buildResultExpression(last, needResult, tempName))); break; default: // With a valid tree we should never get here. throw new IllegalStateException("Unexpected."); } Node ifNode; if (falseExpr.hasChildren()) { ifNode = new Node(Token.IF, cond, trueExpr, falseExpr); } else { ifNode = new Node(Token.IF, cond, trueExpr); } if (needResult) { Node tempVarNode = new Node(Token.VAR, Node.newString(Token.NAME, tempName)); Node injectionPointParent = injectionPoint.getParent(); injectionPointParent.addChildBefore(tempVarNode, injectionPoint);

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> injectionPointParent.addChildAfter(ifNode, tempVarNode); // Replace the expression with the temporary name. Node replacementValueNode = Node.newString(Token.NAME, tempName); parent.replaceChild(expr, replacementValueNode); } else { // Only conditionals that are the direct child of an expression statement // don't need results, for those simply replace the expression statement. Preconditions.checkArgument(parent.getType() == Token.EXPR_RESULT); Node gramps = parent.getParent(); gramps.replaceChild(parent, ifNode); } return ifNode; } /** * Create an expression tree for an expression. * If the result of the expression is needed, then: * ASSIGN * tempName * expr * otherwise, simply: * expr */ private static Node buildResultExpression( Node expr, boolean needResult, String tempName) { if (needResult) { return new Node(Token.ASSIGN, Node.newString(Token.NAME, tempName), expr); } else { return expr; } } /** * @param expr The expression to extract. * @param injectionPoint The node before which to added the extracted * expression. * @return The extract statement node. */ private Node extractExpression(Node expr, Node injectionPoint) { Node parent = expr.getParent(); // The temp value is known to be constant. String tempName = getTempConstantValueName(); // Replace the expression with the temporary name. Node replacementValueNode = Node.newString(Token.NAME, tempName); parent.replaceChild(expr, replacementValueNode); // Re-add the expression in the declaration of the temporary name. Node tempNameNode = Node.newString(Token.NAME, tempName); tempNameNode.addChildrenToBack(expr); Node tempVarNode = new Node(Token.VAR, tempNameNode); Node injectionPointParent = injectionPoint.getParent(); injectionPointParent.addChildBefore(tempVarNode, injectionPoint); // If it is ASSIGN_XXX we need to assign it back to the original value. // Note that calling the temp constant is a lie in this case, but we do know // that

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> it is not modified until after the exposed expression. if (NodeUtil.isAssignmentOp(parent) && !NodeUtil.isAssign(parent)) { Node gParent = parent.getParent(); Node assignBack = new Node(Token.ASSIGN, expr.cloneTree(), tempNameNode.cloneNode()); if (NodeUtil.isExpressionNode(gParent)) { gParent.getParent().addChildAfter( NodeUtil.newExpr(assignBack), gParent); } else { // TODO(user): Use comma here sucks. We might close some accuracy // in flow sensitive passes but as far as I know it is unavoidable. Node comma = new Node(Token.COMMA); gParent.replaceChild(parent, comma); comma.addChildrenToFront(assignBack); comma.addChildrenToFront(parent); } } return tempVarNode; } /** * Rewrite the call so "this" is preserved. * a.b(c); * becomes: * var temp1 = a; * var temp0 = temp1.b; * temp0.call(temp1,c); * * @return The replacement node. */ private Node rewriteCallExpression(Node call, DecompositionState state) { Preconditions.checkArgument(call.getType() == Token.CALL); Node first = call.getFirstChild(); Preconditions.checkArgument(NodeUtil.isGet(first)); // Extracts the expression representing the function to call. For example: // "a['b'].c" from "a['b'].c()" Node getVarNode = extractExpression( first, state.extractBeforeStatement); state.extractBeforeStatement = getVarNode; // Extracts the object reference to be used as "this". For example: // "a['b']" from "a['b'].c" Node getExprNode = getVarNode.getFirstChild().getFirstChild(); Preconditions.checkArgument(NodeUtil.isGet(getExprNode)); Node thisVarNode = extractExpression( getExprNode.getFirstChild(), state.extractBeforeStatement); state.extractBeforeStatement = thisVarNode; // Rewrite the CALL expression. Node thisNameNode = thisVarNode.getFirstChild(); Node

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> functionNameNode = getVarNode.getFirstChild(); // CALL // GETPROP // functionName // "call" // thisName // original-parameter1 // original-parameter2 // ... Node newCall = new Node(Token.CALL, new Node(Token.GETPROP, functionNameNode.cloneNode(), Node.newString("call")), thisNameNode.cloneNode(), call.getLineno(), call.getCharno()); // Throw away the call name call.removeFirstChild(); if (call.hasChildren()) { // Add the call parameters to the new call. newCall.addChildrenToBack(call.removeChildren()); } // Replace the call. Node callParent = call.getParent(); callParent.replaceChild(call, newCall); return newCall; } private String tempNamePrefix = "JSCompiler_temp_"; /** * Allow the temp name to be overriden to make tests more readable. */ @VisibleForTesting public void setTempNamePrefix(String tempNamePrefix) { this.tempNamePrefix = tempNamePrefix; } /** * Create a unique temp name. */ private String getTempValueName(){ return tempNamePrefix + safeNameIdSupplier.get(); } /** * Create a constant unique temp name. */ private String getTempConstantValueName(){ String sName = tempNamePrefix + "const_" + safeNameIdSupplier.get(); this.knownConstants.add(sName); return sName; } /** * @return For the subExpression, find the nearest statement Node before which * it can be inlined. Null if no such location can be found. */ static Node findInjectionPoint(Node subExpression) { Node expressionRoot = findExpressionRoot(subExpression); Preconditions.checkNotNull(expressionRoot); Node injectionPoint = expressionRoot; Node parent = injectionPoint.getParent(); while (parent.getType() == Token.LABEL) { injectionPoint = parent; parent = injectionPoint.getParent(); } Preconditions.checkState( NodeUtil.isStatementBlock(injectionPoint.getParent())); return injectionPoint; } /** * @return Whether the node is a conditional

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> op. */ private static boolean isConditionalOp(Node n) { switch(n.getType()) { case Token.HOOK: case Token.AND: case Token.OR: return true; default: return false; } } /** * @return The statement containing the expression. null if subExpression * is not contain by in by a Node where inlining is known to be possible. * For example, a WHILE node condition expression. */ static Node findExpressionRoot(Node subExpression) { Node child = subExpression; for (Node parent : child.getAncestors()) { int parentType = parent.getType(); switch (parentType) { // Supported expression roots: // SWITCH and IF can have multiple children, but the CASE, DEFAULT, // or BLOCK will be encountered first for any of the children other // than the condition. case Token.EXPR_RESULT: case Token.IF: case Token.SWITCH: case Token.RETURN: case Token.VAR: Preconditions.checkState(child == parent.getFirstChild()); return parent; // Any of these indicate an unsupported expression: case Token.SCRIPT: case Token.BLOCK: case Token.LABEL: case Token.CASE: case Token.DEFAULT: return null; } child = parent; } throw new IllegalStateException("Unexpected AST structure."); } /** * Determine whether a expression is movable, or can be be made movable be * decomposing the containing expression. * * An subExpression is MOVABLE if it can be replaced with a temporary holding * its results and moved to immediately before the root of the expression. * There are three conditions that must be met for this to occur: * 1) There must be a location to inject a statement for the expression. For * example, this condition can not be met if the expression is a loop * condition or CASE condition. * 2) If the expression can be affect by side-effects, there can not be a * side-effect between original location and the expression root. * 3) If the expression has side-effects, there can not be any other * expression that can be effected between the original location and the * expression root. * *

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> this.ast = ast; this.name = inputName; this.isExtern = isExtern; } public CompilerInput(JSSourceFile file) { this(file, false); } public CompilerInput(JSSourceFile file, boolean isExtern) { this.ast = new JsAst(file); this.name = file.getName(); this.isExtern = isExtern; } /** Returns a name for this input. Must be unique across all inputs. */ public String getName() { return name; } @Override public Node getAstRoot(AbstractCompiler compiler) { return ast.getAstRoot(compiler); } @Override public void clearAst() { ast.clearAst(); } @Override public SourceFile getSourceFile() { return ast.getSourceFile(); } @Override public void setSourceFile(SourceFile file) { ast.setSourceFile(file); } /** Returns the SourceAst object on which this input is based. */ public SourceAst getSourceAst() { return ast; } /** Gets a list of types depended on by this input. */ public Set<String> getRequires(AbstractCompiler compiler) { if (getAstRoot(compiler) != null) { DepsFinder deps = new DepsFinder(compiler, true); NodeTraversal.traverse(compiler, getAstRoot(compiler), deps); requires.addAll(deps.types); return requires; } else { return ImmutableSet.<String>of(); } } /** Gets a list of types provided by this input. */ public Set<String> getProvides(AbstractCompiler compiler) { if (getAstRoot(compiler) != null) { DepsFinder deps = new DepsFinder(compiler, false); NodeTraversal.traverse(compiler, getAstRoot(compiler), deps); provides.addAll(deps.types); return provides; } else { return ImmutableSet.<String>of(); } } private class DepsFinder extends AbstractShallowCallback { private boolean findRequire; private List<String> types; private CodingConvention codingConvention; DepsFinder(AbstractCompiler compiler, boolean findRequire) { this.findRequire = findRequire; this.

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>codingConvention = compiler.getCodingConvention(); this.types = Lists.newArrayList(); } public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.CALL: String className = findRequire ? codingConvention.extractClassNameIfRequire(n, parent) : codingConvention.extractClassNameIfProvide(n, parent); if (className != null) { types.add(className); } break; } } } /** * Gets the source line for the indicated line number. * * @param lineNumber the line number, 1 being the first line of the file. * @return The line indicated. Does not include the newline at the end * of the file. Returns {@code null} if it does not exist, * or if there was an IO exception. */ public String getLine(int lineNumber) { return getSourceFile().getLine(lineNumber); } /** * Get a region around the indicated line number. The exact definition of a * region is implementation specific, but it must contain the line indicated * by the line number. A region must not start or end by a carriage return. * * @param lineNumber the line number, 1 being the first line of the file. * @return The line indicated. Returns {@code null} if it does not exist, * or if there was an IO exception. */ public Region getRegion(int lineNumber) { return getSourceFile().getRegion(lineNumber); } public String getCode() throws IOException { return getSourceFile().getCode(); } /** Returns the module to which the input belongs. */ public JSModule getModule() { return module; } /** Sets the module to which the input belongs. */ public void setModule(JSModule module) { // An input may only belong to one module. Preconditions.checkArgument( module == null || this.module == null || this.module == module); this.module = module; } public boolean isExtern() { return isExtern; } }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> abstract T processSwitchCase(SwitchCase caseNode); abstract T processSwitchStatement(SwitchStatement statementNode); abstract T processThrowStatement(ThrowStatement statementNode); abstract T processTryStatement(TryStatement statementNode); abstract T processUnaryExpression(UnaryExpression exprNode); abstract T processVariableDeclaration(VariableDeclaration declarationNode); abstract T processVariableInitializer(VariableInitializer initializerNode); abstract T processWhileLoop(WhileLoop loopNode); abstract T processWithStatement(WithStatement statementNode); abstract T processIllegalToken(AstNode node); public T process(AstNode node) { switch (node.getType()) { case Token.ADD: case Token.AND: case Token.BITAND: case Token.BITOR: case Token.BITXOR: case Token.COMMA: case Token.DIV: case Token.EQ: case Token.GE: case Token.GT: case Token.IN: case Token.INSTANCEOF: case Token.LE: case Token.LSH: case Token.LT: case Token.MOD: case Token.MUL: case Token.NE: case Token.OR: case Token.RSH: case Token.SHEQ: case Token.SHNE: case Token.SUB: case Token.URSH: return processInfixExpression((InfixExpression) node); case Token.ARRAYLIT: return processArrayLiteral((ArrayLiteral) node); case Token.ASSIGN: case Token.ASSIGN_ADD: case Token.ASSIGN_BITAND: case Token.ASSIGN_BITOR: case Token.ASSIGN_BITXOR: case Token.ASSIGN_DIV: case Token.ASSIGN_LSH: case Token.ASSIGN_MOD: case Token.ASSIGN_MUL: case Token.ASSIGN_RSH: case Token.ASSIGN_SUB: case Token.ASSIGN_URSH: return processAssignment((Assignment) node); case Token.BITNOT: case Token.DEC: case Token.DELPROP: case Token.INC: case Token.NEG: case Token.NOT: case Token.POS: case Token.TYPEOF: case Token.VOID: return processUnaryExpression((UnaryExpression) node); case Token.BLOCK: if (node instanceof Block

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>) { return processBlock((Block) node); } else if (node instanceof Scope) { return processScope((Scope) node); } else { throw new IllegalStateException("Unexpected node type. class: " + node.getClass() + " type: " + Token.typeToName(node.getType())); } case Token.BREAK: return processBreakStatement((BreakStatement) node); case Token.CALL: return processFunctionCall((FunctionCall) node); case Token.CASE: case Token.DEFAULT: return processSwitchCase((SwitchCase) node); case Token.CATCH: case Token.FINALLY: return processCatchClause((CatchClause) node); case Token.COLON: return processObjectProperty((ObjectProperty) node); case Token.CONTINUE: return processContinueStatement((ContinueStatement) node); case Token.DO: return processDoLoop((DoLoop) node); case Token.EMPTY: return processEmptyExpression((EmptyExpression) node); case Token.EXPR_RESULT: case Token.EXPR_VOID: if (node instanceof ExpressionStatement) { return processExpressionStatement((ExpressionStatement) node); } else if (node instanceof LabeledStatement) { return processLabeledStatement((LabeledStatement) node); } else { throw new IllegalStateException("Unexpected node type. class: " + node.getClass() + " type: " + Token.typeToName(node.getType())); } case Token.DEBUGGER: case Token.FALSE: case Token.NULL: case Token.THIS: case Token.TRUE: return processKeywordLiteral((KeywordLiteral) node); case Token.FOR: if (node instanceof ForInLoop) { return processForInLoop((ForInLoop) node); } else if (node instanceof ForLoop) { return processForLoop((ForLoop) node); } else { throw new IllegalStateException("Unexpected node type. class: " + node.getClass() + " type: " + Token.typeToName(node.getType())); } case Token.FUNCTION: return processFunctionNode((FunctionNode) node); case Token.GETELEM: return processElementGet((ElementGet) node); case Token.GETPROP: return processPropertyGet

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>((PropertyGet) node); case Token.HOOK: return processConditionalExpression((ConditionalExpression) node); case Token.IF: return processIfStatement((IfStatement) node); case Token.LABEL: return processLabel((Label) node); case Token.LP: return processParenthesizedExpression((ParenthesizedExpression) node); case Token.NAME: return processName((Name) node); case Token.NEW: return processNewExpression((NewExpression) node); case Token.NUMBER: return processNumberLiteral((NumberLiteral) node); case Token.OBJECTLIT: return processObjectLiteral((ObjectLiteral) node); case Token.REGEXP: return processRegExpLiteral((RegExpLiteral) node); case Token.RETURN: return processReturnStatement((ReturnStatement) node); case Token.SCRIPT: return processAstRoot((AstRoot) node); case Token.STRING: return processStringLiteral((StringLiteral) node); case Token.SWITCH: return processSwitchStatement((SwitchStatement) node); case Token.THROW: return processThrowStatement((ThrowStatement) node); case Token.TRY: return processTryStatement((TryStatement) node); case Token.VAR: if (node instanceof VariableDeclaration) { return processVariableDeclaration((VariableDeclaration) node); } else if (node instanceof VariableInitializer) { return processVariableInitializer((VariableInitializer) node); } else { throw new IllegalStateException("Unexpected node type. class: " + node.getClass() + " type: " + Token.typeToName(node.getType())); } case Token.WHILE: return processWhileLoop((WhileLoop) node); case Token.WITH: return processWithStatement((WithStatement) node); } return processIllegalToken(node); } }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> "); break; case 4: sb.append(" "); break; } } /** * Adds a new tracing statistic to a trace * * @param tracingStatistic to enable a run * @return The index of this statistic (for use with stat.extraInfo()), or * -1 if the statistic is not enabled. */ static int addTracingStatistic(TracingStatistic tracingStatistic) { // Check to see if we can enable the tracing statistic before actually // adding it. if (tracingStatistic.enable()) { // No synchronization needed, since this is a copy-on-write array. extraTracingStatistics.add(tracingStatistic); // 99.9% of the time, this will be O(1) and return extraTracingStatistics.length - 1 return extraTracingStatistics.lastIndexOf(tracingStatistic); } else { return -1; } } /** * For testing purposes only. These removes all current tracers. Severe errors can occur * if there are any active tracers going on when this is called. * * The test suite uses this to remove any tracers that it has added. */ @VisibleForTesting static void clearTracingStatisticsTestingOnly() { extraTracingStatistics.clear(); } /** * Stop the trace. * This may only be done once and must be done from the same thread * that started it. * @param silence_threshold Traces for time less than silence_threshold * ms will be left out of the trace report. A value of -1 indicates * that the current ThreadTrace silence_threshold should be used. * @return The time that this trace actually ran */ long stop(int silence_threshold) { Preconditions.checkState(Thread.currentThread() == startThread); ThreadTrace trace = getThreadTrace(); // Do nothing if the thread trace was not initialized. if (!trace.isInitialized()) { return 0; } stopTimeMs = clock.currentTimeMillis(); if (extraTracingValues != null) { // We use extraTracingValues.length rather than extraTracingStatistics.size() because // a new statistic may have been added for (int i = 0; i < extraTracingValues.length; i++) {

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>Statistics.get(i).getUnits()); sb.append("; "); } } } sb.append(indent); sb.append(tracer.toString()); return sb.toString(); } } /** Stores a thread's Trace */ static final class ThreadTrace { /** Events taking less than this number of milliseconds are not reported. */ int defaultSilenceThreshold; // non-final /** The Events corresponding to each startEvent/stopEvent */ final ArrayList<Event> events = new ArrayList<Event>(); /** Tracers that have not had their .stop() called */ final HashSet<Tracer> outstandingEvents = new HashSet<Tracer>(); /** Map from type to Stat object */ final Map<String, Stat> stats = new HashMap<String, Stat>(); /** * True if {@code outstandingEvents} has been cleared because we exceeded * the max trace limit. */ boolean isOutstandingEventsTruncated = false; /** * True if {@code events} has been cleared because we exceeded the max * trace limit. */ boolean isEventsTruncated = false; /** * Set to true if {@link Tracer#initCurrentThreadTrace()} was called by * the current thread. */ boolean isInitialized = false; /** * Whether pretty printing is enabled for the trace. */ boolean prettyPrint = false; /** Initialize the trace. */ void init() { isInitialized = true; } /** Is initialized? */ boolean isInitialized() { return isInitialized; } /** * Called by the constructor {@link Tracer#Tracer(String, String)} to create * a start event. */ void startEvent(Tracer t) { events.add(new Event(true, t)); boolean notAlreadyOutstanding = outstandingEvents.add(t); Preconditions.checkState(notAlreadyOutstanding); } /** * Called by {@link Tracer#stop()} to create a stop event. */ void endEvent(Tracer t, int silenceThreshold) { boolean wasOutstanding = outstandingEvents.remove(t); if (!wasOutstanding) { if (isOutstandingEventsTruncated) { // The events stack overflowed and was truncated, so just log a // warning. Otherwise, we get an exception which is extremely

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> // confusing. logger.log(Level.WARNING, "event not found, probably because the event stack " + "overflowed and was truncated", new Throwable()); } else { // throw an exception if the event was not found and the events stack // is pristine throw new IllegalStateException(); } } long elapsed = t.stopTimeMs - t.startTimeMs; if (silenceThreshold == -1) { // use default silenceThreshold = defaultSilenceThreshold; } if (elapsed < silenceThreshold) { // If this one is silent then we need to remove the start Event boolean removed = false; for (int i = 0; i < events.size(); i++) { Event e = events.get(i); if (e.tracer == t) { Preconditions.checkState(e.isStart); events.remove(i); removed = true; break; } } // Only assert if we didn't find the original and the events // weren't truncated. Preconditions.checkState(removed || isEventsTruncated); } else { events.add(new Event(false, t)); } if (t.type != null) { Stat stat = stats.get(t.type); if (stat == null) { stat = new Stat(); if (!extraTracingStatistics.isEmpty()) { stat.extraInfo = new int[extraTracingStatistics.size()]; } stats.put(t.type, stat); } stat.count++; if (typeToCountMap != null) { typeToCountMap.incrementBy(t.type, 1); } stat.clockTime += elapsed; if (typeToTimeMap != null) { typeToTimeMap.incrementBy(t.type, elapsed); } if (stat.extraInfo != null && t.extraTracingValues != null) { int overlapLength = Math.min(stat.extraInfo.length, t.extraTracingValues.length); for (int i = 0; i < overlapLength; i++) { stat.extraInfo[i] += t.extraTracingValues[i]; AtomicTracerStatMap map = extraTracingStatistics.get(i).getTracingStat(); if (map !=

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> !(parent.getType() == Token.VAR && !parent.hasOneChild())) { docInfo = parent.getJSDocInfo(); } // Try to find the type of the NAME. JSType varType = n.getJSType(); if (varType == null && parent.getType() == Token.FUNCTION) { varType = parent.getJSType(); } // If we have no type to attach JSDocInfo to, then there's nothing // we can do. if (varType == null || docInfo == null) { return; } // Dereference the type. If the result is not an object, or already // has docs attached, then do nothing. ObjectType objType = dereferenceToObject(varType); if (objType == null || objType.getJSDocInfo() != null) { return; } attachJSDocInfoToNominalTypeOrShape(objType, docInfo, n.getString()); break; case Token.GETPROP: // Infer JSDocInfo on properties. // There are two ways to write doc comments on a property. // // 1) // /** @deprecated */ // obj.prop = ... // // 2) // /** @deprecated */ // obj.prop; if (NodeUtil.isExpressionNode(parent) || (parent.getType() == Token.ASSIGN && parent.getFirstChild() == n)) { docInfo = n.getJSDocInfo(); if (docInfo == null) { docInfo = parent.getJSDocInfo(); } if (docInfo != null) { ObjectType lhsType = dereferenceToObject(n.getFirstChild().getJSType()); if (lhsType != null) { // Put the JSDoc in the property slot, if there is one. String propName = n.getLastChild().getString(); if (lhsType.hasOwnProperty(propName)) { lhsType.setPropertyJSDocInfo(propName, docInfo, inExterns); } // Put the JSDoc in any constructors or function shapes as well. ObjectType propType = dereferenceToObject(lhsType.getPropertyType(propName)); if (propType != null) { attachJ

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> path through // the code identical to how it's been for years. this.outputCharsetEncoder = null; } else { this.outputCharsetEncoder = outputCharset.newEncoder(); } } CodeGenerator(CodeConsumer consumer, Charset outputCharset) { this(consumer, outputCharset, true); } CodeGenerator(CodeConsumer consumer) { this(consumer, null, false); } void add(String str) { cc.add(str); } private void addIdentifier(String identifier) { cc.addIdentifier(identifierEscape(identifier)); } void add(Node n) { add(n, Context.OTHER); } void add(Node n, Context context) { if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState(childCount == 2); int p = NodeUtil.precedence(type); addLeftExpr(first, p, context); cc.addOp(opstr, true); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); // Handle associativity. // e.g. if the parse tree is a * (b * c), // we can simply generate a * b * c. if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { // Assignments are the only right-associative binary operators addExpr(last, p, rhsContext); } else { addExpr(last, p + 1, rhsContext); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { Preconditions.checkState(first.getNext().getType() ==

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> Token.BLOCK && first.getNext().getChildCount() <= 1); Preconditions.checkState(childCount >= 2 && childCount <= 3); add("try"); add(first, Context.PRESERVE_BLOCK); // second child contains the catch block, or nothing if there // isn't a catch block Node catchblock = first.getNext().getFirstChild(); if (catchblock != null) { add(catchblock); } if (childCount == 3) { add("finally"); add(last, Context.PRESERVE_BLOCK); } break; } case Token.CATCH: Preconditions.checkState(childCount == 3); if (first.getNext().getType() != Token.EMPTY) { throw new Error("Catch conditions not suppored because I think" + " that it may be a netscape only feature."); } add("catch("); add(first); add(")"); add(last, Context.PRESERVE_BLOCK); break; case Token.THROW: Preconditions.checkState(childCount == 1); add("throw"); add(first); // Must have a ';' after a throw statement, otherwise safari can't // parse this. cc.endStatement(true); break; case Token.RETURN: add("return"); if (childCount == 1) { add(first); } else { Preconditions.checkState(childCount == 0); } cc.endStatement(); break; case Token.VAR: if (first != null) { add("var "); addList(first, false, getContextForNoInOperator(context)); } break; case Token.NAME: if (first == null || first.getType() == Token.EMPTY) { addIdentifier(n.getString()); } else { Preconditions.checkState(childCount == 1); addIdentifier(n.getString()); cc.addOp("=", true); if (first.getType() == Token.COMMA) { addExpr(first, NodeUtil.precedence(Token.ASSIGN)); } else { // Add expression, consider nearby code at lowest level of // precedence. addExpr(first, 0, getContextForNoInOperator

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>(context)); } } break; case Token.ARRAYLIT: add("["); addList(first, (int[]) n.getProp(Node.SKIP_INDEXES_PROP)); add("]"); break; case Token.LP: add("("); addList(first); add(")"); break; case Token.COMMA: addList(first, false, context); break; case Token.NUMBER: Preconditions.checkState(childCount == 0); cc.addNumber(n.getDouble()); break; case Token.TYPEOF: case Token.VOID: case Token.NOT: case Token.BITNOT: case Token.POS: case Token.NEG: { // All of these unary operators are right-associative Preconditions.checkState(childCount == 1); cc.addOp(NodeUtil.opToStrNoFail(type), false); addExpr(first, NodeUtil.precedence(type)); break; } case Token.HOOK: { Preconditions.checkState(childCount == 3); int p = NodeUtil.precedence(type); addLeftExpr(first, p + 1, context); cc.addOp("?", true); addExpr(first.getNext(), p); cc.addOp(":", true); addExpr(last, p); break; } case Token.REGEXP: if (first.getType() != Token.STRING || last.getType() != Token.STRING) { throw new Error("Expected children to be strings"); } String regexp = regexpEscape(first.getString(), outputCharsetEncoder); // I only use one .add because whitespace matters if (childCount == 2) { add(regexp + last.getString()); } else { Preconditions.checkState(childCount == 1); add(regexp); } break; case Token.GET_REF: add(first); break; case Token.REF_SPECIAL: Preconditions.checkState(childCount == 1); add(first); add("."); add((String) n.getProp(Node.NAME_PROP)); break; case Token.FUNCTION: Preconditions.checkState(childCount == 3);

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> boolean funcNeedsParens = (context == Context.START_OF_EXPR); if (funcNeedsParens) { add("("); } add("function"); add(first); add(first.getNext()); add(last, Context.PRESERVE_BLOCK); cc.endFunction(context == Context.STATEMENT); if (funcNeedsParens) { add(")"); } break; case Token.SCRIPT: case Token.BLOCK: { boolean stripBlock = n.isSyntheticBlock() || ((context != Context.PRESERVE_BLOCK) && (n.getChildCount() < 2)); if (!stripBlock) { cc.beginBlock(); } for (Node c = first; c != null; c = c.getNext()) { add(c, Context.STATEMENT); // VAR doesn't include ';' since it gets used in expressions if (c.getType() == Token.VAR) { cc.endStatement(); } if (c.getType() == Token.FUNCTION) { cc.maybeLineBreak(); } // Prefer to break lines in between top-level statements // because top level statements are more homogeneous. if (type == Token.SCRIPT) { cc.notePreferredLineBreak(); } } if (!stripBlock) { cc.endBlock(context == Context.STATEMENT); } break; } case Token.FOR: if (childCount == 4) { add("for("); if (first.getType() == Token.VAR) { add(first, Context.IN_FOR_INIT_CLAUSE); } else { addExpr(first, 0, Context.IN_FOR_INIT_CLAUSE); } add(";"); add(first.getNext()); add(";"); add(first.getNext().getNext()); add(")"); addNonEmptyExpression( last, getContextForNonEmptyExpression(context), false); } else { Preconditions.checkState(childCount == 3); add("for("); add(first); add("in"); add(first.getNext()); add(")"); addNonEmptyExpression( last, getContextForNonEmptyExpression(context), false); } break

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>; case Token.DO: Preconditions.checkState(childCount == 2); add("do"); addNonEmptyExpression(first, Context.OTHER, false); add("while("); add(last); add(")"); cc.endStatement(); break; case Token.WHILE: Preconditions.checkState(childCount == 2); add("while("); add(first); add(")"); addNonEmptyExpression( last, getContextForNonEmptyExpression(context), false); break; case Token.EMPTY: Preconditions.checkState(childCount == 0); break; case Token.GETPROP: { Preconditions.checkState(childCount == 2); Preconditions.checkState(last.getType() == Token.STRING); boolean needsParens = (first.getType() == Token.NUMBER); if (needsParens) { add("("); } addLeftExpr(first, NodeUtil.precedence(type), context); if (needsParens) { add(")"); } add("."); addIdentifier(last.getString()); break; } case Token.GETELEM: Preconditions.checkState(childCount == 2); addLeftExpr(first, NodeUtil.precedence(type), context); add("["); add(first.getNext()); add("]"); break; case Token.WITH: Preconditions.checkState(childCount == 2); add("with("); add(first); add(")"); addNonEmptyExpression( last, getContextForNonEmptyExpression(context), false); break; case Token.INC: case Token.DEC: { Preconditions.checkState(childCount == 1); String o = type == Token.INC ? "++" : "--"; int postProp = n.getIntProp(Node.INCRDECR_PROP, 0); // A non-zero post-prop value indicates a post inc/dec, default of zero // is a pre-inc/dec. if (postProp != 0) { addLeftExpr(first, NodeUtil.precedence(type), context); cc.addOp(o, false); } else { cc.addOp(o, false);

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> add(first); } break; } case Token.CALL: // If the left hand side of the call is a direct reference to eval, // then it must have a DIRECT_EVAL annotation. If it does not, then // that means it was originally an indirect call to eval, and that // indirectness must be preserved. if (first.getType() == Token.NAME && "eval".equals(first.getString()) && !first.getBooleanProp(Node.DIRECT_EVAL)) { add("(0,eval)"); } else { addLeftExpr(first, NodeUtil.precedence(type), context); } add("("); addList(first.getNext()); add(")"); break; case Token.IF: boolean hasElse = childCount == 3; boolean ambiguousElseClause = context == Context.BEFORE_DANGLING_ELSE && !hasElse; if (ambiguousElseClause) { cc.beginBlock(); } add("if("); add(first); add(")"); if (hasElse) { addNonEmptyExpression( first.getNext(), Context.BEFORE_DANGLING_ELSE, false); add("else"); addNonEmptyExpression( last, getContextForNonEmptyExpression(context), false); } else { addNonEmptyExpression(first.getNext(), Context.OTHER, false); Preconditions.checkState(childCount == 2); } if (ambiguousElseClause) { cc.endBlock(); } break; case Token.NULL: case Token.THIS: case Token.FALSE: case Token.TRUE: Preconditions.checkState(childCount == 0); add(Node.tokenToName(type)); break; case Token.CONTINUE: Preconditions.checkState(childCount <= 1); add("continue"); if (childCount == 1) { add(" "); add(first); } cc.endStatement(); break; case Token.DEBUGGER: Preconditions.checkState(childCount == 0); add("debugger"); cc.endStatement(); break; case Token.BREAK: Preconditions.checkState(childCount <= 1); add("break"); if (childCount

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> == 1) { add(" "); add(first); } cc.endStatement(); break; case Token.EXPR_VOID: case Token.EXPR_RESULT: if (type == Token.EXPR_VOID && validation) { throw new Error("Unexpected EXPR_VOID. Should be EXPR_RESULT."); } Preconditions.checkState(childCount == 1); add(first, Context.START_OF_EXPR); cc.endStatement(); break; case Token.NEW: add("new "); int precedence = NodeUtil.precedence(type); // If the first child contains a CALL, then claim higher precedence // to force parens. Otherwise, when parsed, NEW will bind to the // first viable parens if (NodeUtil.containsCall(first)) { precedence = NodeUtil.precedence(first.getType()) + 1; } addExpr(first, precedence); // '()' is optional when no arguments are present Node next = first.getNext(); if (next != null) { add("("); addList(next); add(")"); } break; case Token.STRING: Preconditions.checkState(childCount == 0); add(jsString(n.getString(), outputCharsetEncoder)); break; case Token.DELPROP: Preconditions.checkState(childCount == 1); add("delete "); add(first); break; case Token.OBJECTLIT: { Preconditions.checkState(childCount % 2 == 0); boolean needsParens = (context == Context.START_OF_EXPR); if (needsParens) { add("("); } add("{"); for (Node c = first; c != null; c = c.getNext().getNext()) { if (c != first) { cc.listSeparator(); } // Object literal property names don't have to be quoted if they are // not JavaScript keywords if (c.getType() == Token.STRING && !TokenStream.isKeyword(c.getString()) && TokenStream.isJSIdentifier(c.getString()) && // do not encode literally any non-literal characters that were // unicode escaped. NodeUtil.isLatin(c.getString()))

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> { add(c.getString()); } else { addExpr(c, 1); } add(":"); addExpr(c.getNext(), 1); } add("}"); if (needsParens) { add(")"); } break; } case Token.SWITCH: add("switch("); add(first); add(")"); cc.beginBlock(); addAllSiblings(first.getNext()); cc.endBlock(context == Context.STATEMENT); break; case Token.CASE: Preconditions.checkState(childCount == 2); add("case "); add(first); addCaseBody(last); break; case Token.DEFAULT: Preconditions.checkState(childCount == 1); add("default"); addCaseBody(first); break; case Token.LABEL: Preconditions.checkState(childCount == 2); add(first); add(":"); addNonEmptyExpression( last, getContextForNonEmptyExpression(context), true); break; // This node is auto generated in anonymous functions and should just get // ignored for our purposes. case Token.SETNAME: break; default: throw new Error("Unknown type " + type + "\n" + n.toStringTree()); } cc.endSourceMapping(n); } /** * Adds a block or expression, substituting a VOID with an empty statement. * This is used for "for (...);" and "if (...);" type statements. * * @param n The node to print. * @param context The context to determine how the node should be printed. */ private void addNonEmptyExpression( Node n, Context context, boolean allowNonBlockChild) { Node nodeToProcess = n; if (!allowNonBlockChild && n.getType() != Token.BLOCK) { if (validation) { throw new Error("Missing BLOCK child."); } } // Strip unneeded blocks, that is blocks with <2 children unless // the CodePrinter specifically wants to keep them. if (n.getType() == Token.BLOCK ) { int count = getNonEmptyChildCount(n); if (count == 0) { cc.endStatement(true); return;

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> } if (count == 1) { // Hack around a couple of browser bugs: // Safari needs a block around function declarations. // IE6/7 needs a block around DOs. Node firstAndOnlyChild = getFirstNonEmptyChild(n); boolean alwaysWrapInBlock = cc.shouldPreserveExtraBlocks(); if (alwaysWrapInBlock || firstAndOnlyChild.getType() == Token.FUNCTION || firstAndOnlyChild.getType() == Token.DO) { cc.beginBlock(); add(firstAndOnlyChild, Context.STATEMENT); cc.maybeLineBreak(); cc.endBlock(context == Context.STATEMENT); return; } else { // Continue with the only child. nodeToProcess = firstAndOnlyChild; } } } if (nodeToProcess.getType() == Token.EMPTY) { cc.endStatement(true); } else { add(nodeToProcess, context); // VAR doesn't include ';' since it gets used in expressions - so any // VAR in a statement context needs a call to endStatement() here. if (nodeToProcess.getType() == Token.VAR) { cc.endStatement(); } } } /** * Adds a node at the left-hand side of an expression. Unlike * {@link #addExpr(Node,int)}, this preserves information about the context. * * The left side of an expression is special because in the JavaScript * grammar, certain tokens may be parsed differently when they are at * the beginning of a statement. For example, "{}" is parsed as a block, * but "{'x': 'y'}" is parsed as an object literal. */ void addLeftExpr(Node n, int minPrecedence, Context context) { addExpr(n, minPrecedence, context); } void addExpr(Node n, int minPrecedence) { addExpr(n, minPrecedence, Context.OTHER); } private void addExpr(Node n, int minPrecedence, Context context) { if ((NodeUtil.precedence(n.getType()) < minPrecedence) || ((context == Context.IN_FOR_INIT_CLAUSE) && (n.getType() == Token.IN))

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> s = scope.getParent(); if (s == null) { throw new IllegalArgumentException("Var is not local"); } while (s.getParent() != null) { num += s.getVarCount(); s = s.getParent(); } return num; } /** * Returns whether this is a global variable. */ public boolean isGlobal() { return scope.isGlobal(); } /** * Returns whether this is a local variable. */ public boolean isLocal() { return scope.isLocal(); } /** * Returns whether this is defined in an extern file. */ boolean isExtern() { return input == null || input.isExtern(); } /** * Returns {@code true} if the variable is declared as a constant, * based on the value reported by {@code NodeUtil}. */ public boolean isConst() { return NodeUtil.isConstantName(nameNode); } /** * Returns {@code true} if the variable is declared as a define. * A variable is a define if it is annotaed by {@code @define}. */ public boolean isDefine() { return isDefine; } public Node getInitialValue() { Node parent = getParentNode(); return parent.getType() == Token.FUNCTION ? parent : nameNode.getFirstChild(); } /** * Gets this variable's type. To know whether this type has been inferred, * see {@code #isInferred()}. */ public JSType getType() { return type; } /** * Returns the name node that produced this variable. */ public Node getNameNode() { return nameNode; } /** * Gets the JSDocInfo for the variable. */ public JSDocInfo getJSDocInfo() { return info; } /** * Sets this variable's type. * @throws IllegalStateException if the variable's type is not inferred */ void setType(JSType type) { Preconditions.checkState(isTypeInferred()); this.type = type; } /** * Returns whether this variable's type is inferred. To get the variable's * type, see {@link #getType()}. */ public boolean isTypeInferred() { return typeInferred; }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> public String getInputName() { if (input == null) return "<non-file>"; else return input.getName(); } public boolean isNoShadow() { if (info != null && info.isNoShadow()) { return true; } else { return false; } } @Override public boolean equals(Object other) { if (!(other instanceof Var)) { return false; } Var otherVar = (Var) other; return otherVar.nameNode == nameNode; } @Override public int hashCode() { return nameNode.hashCode(); } @Override public String toString() { return "Scope.Var " + name; } } /** * Creates a Scope given the parent Scope and the root node of the scope. * @param parent The parent Scope. Cannot be null. * @param rootNode Typically the FUNCTION node. */ Scope(Scope parent, Node rootNode) { Preconditions.checkNotNull(parent); Preconditions.checkArgument(rootNode != parent.rootNode); this.parent = parent; this.rootNode = rootNode; JSType nodeType = rootNode.getJSType(); if (nodeType != null && nodeType instanceof FunctionType) { thisType = ((FunctionType) nodeType).getTypeOfThis(); } else { thisType = parent.thisType; } this.isBottom = false; } /** * Creates a global Scope. * @param rootNode Typically the global BLOCK node. */ Scope(Node rootNode, AbstractCompiler compiler) { this.parent = null; this.rootNode = rootNode; thisType = compiler.getTypeRegistry().getNativeObjectType(GLOBAL_THIS); this.isBottom = false; } /** * Creates a empty Scope (bottom of the lattice). * @param rootNode Typically a FUNCTION node or the global BLOCK node. * @param thisType the type of {@code this} in this scope */ Scope(Node rootNode, ObjectType thisType) { this.parent = null; this.rootNode = rootNode; this.thisType = thisType; this.isBottom = true; } /** Whether this is the bottom of the lattice. */ boolean isBottom

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>() { return isBottom; } /** * Gets the container node of the scope. This is typically the FUNCTION * node or the global BLOCK/SCRIPT node. */ public Node getRootNode() { return rootNode; } public Scope getParent() { return parent; } Scope getGlobalScope() { Scope result = this; while (result.getParent() != null) { result = result.getParent(); } return result; } @Override public StaticScope<JSType> getParentScope() { return parent; } /** * Gets the type of {@code this} in the current scope. */ public ObjectType getTypeOfThis() { return thisType; } /** * Declares a variable whose type is inferred. * * @param name name of the variable * @param nameNode the NAME node declaring the variable * @param type the variable's type * @param input the input in which this variable is defined. */ Var declare(String name, Node nameNode, JSType type, CompilerInput input) { return declare(name, nameNode, type, input, true); } /** * Declares a variable. * * @param name name of the variable * @param nameNode the NAME node declaring the variable * @param type the variable's type * @param input the input in which this variable is defined. * @param inferred Whether this variable's type is inferred (as opposed * to declared). */ Var declare(String name, Node nameNode, JSType type, CompilerInput input, boolean inferred) { Preconditions.checkState(name != null && name.length() > 0); // Make sure that it's declared only once Preconditions.checkState(vars.get(name) == null); Var var = new Var(inferred); var.name = name; var.nameNode = nameNode; var.type = type; var.scope = this; var.index = vars.size(); var.input = input; // native variables do not have a name node. // TODO(user): make Var abstract and have NativeVar, NormalVar. JSDocInfo info = NodeUtil.getInfoForNameNode(nameNode); var

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>.isDefine = info != null && info.isDefine(); var.info = info; vars.put(name, var); return var; } /** * Undeclares a variable, to be used when the compiler optimizes out * a variable and removes it from the scope. */ void undeclare(Var var) { Preconditions.checkState(var.scope == this); Preconditions.checkState(vars.get(var.name) == var); vars.remove(var.name); } public StaticSlot<JSType> getSlot(String name) { return getVar(name); } public StaticSlot<JSType> getOwnSlot(String name) { return vars.get(name); } /** * Returns the variable, may be null */ public Var getVar(String name) { Var var = vars.get(name); if (var != null) { return var; } else if (parent != null) { // Recurse up the parent Scope return parent.getVar(name); } else { return null; } } /** * Returns true if a variable is declared. */ public boolean isDeclared(String name, boolean recurse) { Scope scope = this; if (scope.vars.containsKey(name)) return true; if (scope.parent != null && recurse) { return scope.parent.isDeclared(name, recurse); } return false; } /** * Return an iterator over all of the variables declared in this scope. */ public Iterator<Var> getVars() { return vars.values().iterator(); } /** * Returns number of variables in this scope */ public int getVarCount() { return vars.size(); } /** * Returns whether this is the global scope. */ public boolean isGlobal() { return parent == null; } /** * Returns whether this is a local scope (i.e. not the global scope). */ public boolean isLocal() { return !isGlobal(); } }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> edges // on the next iteration. DiGraphNode<N, E> source = workSet.iterator().next(); N sourceValue = source.getValue(); workSet.remove(source); List<DiGraphEdge<N, E>> outEdges = source.getOutEdges(); for (DiGraphEdge<N, E> edge : outEdges) { N destNode = edge.getDestination().getValue(); if (callback.traverseEdge(sourceValue, edge.getValue(), destNode)) { workSet.add(edge.getDestination()); } } } Preconditions.checkState(cycleCount != maxIterations, NON_HALTING_ERROR_MSG); } public static interface EdgeCallback<Node, Edge> { /** * Update the state of the destination node when the given edge * is traversed. For the fixed-point computation to work, only the * destination node may be modified. The source node and the edge must * not be modified. * * @param source The start node. * @param e The edge. * @param destination The end node. * @return Whether the state of the destination node changed. */ boolean traverseEdge(Node source, Edge e, Node destination); } }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>_PARAM = DiagnosticType.warning( "JSC_INEXISTANT_PARAM", "parameter {0} does not appear in {1}''s parameter list"); static final DiagnosticType TYPE_REDEFINITION = DiagnosticType.warning( "JSC_TYPE_REDEFINITION", "attempted re-definition of type {0}\n" + "found : {1}\n" + "expected: {2}"); static final DiagnosticType TEMPLATE_TYPE_DUPLICATED = DiagnosticType.error( "JSC_TEMPLATE_TYPE_DUPLICATED", "Only one parameter type must be the template type"); static final DiagnosticType TEMPLATE_TYPE_EXPECTED = DiagnosticType.error( "JSC_TEMPLATE_TYPE_EXPECTED", "The template type must be a parameter type"); /** * @param fnName The function name. * @param compiler The compiler. * @param errorRoot The node to associate with any warning generated by * this builder. * @param sourceName A source name for associating any warnings that * we have to emit. * @param scope The syntactic scope. */ FunctionTypeBuilder(String fnName, AbstractCompiler compiler, Node errorRoot, String sourceName, Scope scope) { Preconditions.checkNotNull(errorRoot); this.fnName = fnName == null ? "" : fnName; this.codingConvention = compiler.getCodingConvention(); this.typeRegistry = compiler.getTypeRegistry(); this.errorRoot = errorRoot; this.sourceName = sourceName; this.compiler = compiler; this.scope = scope; } /** * Sets the FUNCTION node of this function. */ FunctionTypeBuilder setSourceNode(@Nullable Node sourceNode) { this.sourceNode = sourceNode; return this; } /** * Infer the parameter and return types of a function from * the parameter and return types of the function it is overriding. * * @param oldType The function being overridden. * @param paramsParent The LP node of the function that we're assigning to. * If null, that just means we're not initializing this to a function * literal. */ FunctionTypeBuilder inferFromOverriddenFunction( FunctionType oldType, @Nullable Node paramsParent) {

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>, baseType.toString()); } } else { reportWarning(EXTENDS_WITHOUT_TYPEDEF, fnName); } } // implemented interfaces if (isConstructor || isInterface) { implementedInterfaces = Lists.newArrayList(); for (JSTypeExpression t : info.getImplementedInterfaces()) { ObjectType interType = ObjectType.cast(t.evaluate(scope)); if (interType != null) { implementedInterfaces.add(interType); } else { reportError(BAD_IMPLEMENTED_TYPE, fnName); } } if (baseType != null) { JSType maybeFunctionType = baseType.getConstructor(); if (maybeFunctionType instanceof FunctionType) { FunctionType functionType = baseType.getConstructor(); Iterables.addAll( implementedInterfaces, functionType.getImplementedInterfaces()); } } } else if (info.getImplementedInterfaceCount() > 0) { reportWarning(IMPLEMENTS_WITHOUT_CONSTRUCTOR, fnName); } } return this; } /** * Infers the type of {@code this}. * @param type The type of this. */ FunctionTypeBuilder inferThisType(JSDocInfo info, JSType type) { ObjectType objType = ObjectType.cast(type); if (objType != null && (info == null || !info.hasType())) { thisType = objType; } return this; } /** * Infers the type of {@code this}. * @param info The JSDocInfo for this function. * @param owner The node for the object whose prototype "owns" this function. * For example, {@code A} in the expression {@code A.prototype.foo}. May * be null to indicate that this is not a prototype property. */ FunctionTypeBuilder inferThisType(JSDocInfo info, @Nullable Node owner) { ObjectType maybeThisType = null; if (info != null && info.hasThisType()) { maybeThisType = ObjectType.cast(info.getThisType().evaluate(scope)); } if (maybeThisType != null) { // TODO(user): Doing an instanceof check here is too // restrictive as (Date,Error) is, for instance, an object

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>FunctionType functionType, FunctionType getterType, ObjectType objectType) { functionType.defineDeclaredProperty("getInstance", getterType, false); functionType.defineDeclaredProperty("instance_", objectType, false); } @Override public String getGlobalObject() { return "goog.global"; } private final Set<String> propertyTestFunctions = ImmutableSet.of( "goog.isDef", "goog.isNull", "goog.isDefAndNotNull", "goog.isString", "goog.isNumber", "goog.isBoolean", "goog.isFunction", "goog.isArray", "goog.isObject"); @Override public boolean isPropertyTestFunction(Node call) { Preconditions.checkArgument(call.getType() == Token.CALL); return propertyTestFunctions.contains( call.getFirstChild().getQualifiedName()); } @Override public ObjectLiteralCast getObjectLiteralCast(NodeTraversal t, Node callNode) { Preconditions.checkArgument(callNode.getType() == Token.CALL); Node callName = callNode.getFirstChild(); if (!"goog.reflect.object".equals(callName.getQualifiedName()) || callName.getChildCount() != 2) { return null; } Node typeNode = callName.getNext(); if (!typeNode.isQualifiedName()) { return null; } Node objectNode = typeNode.getNext(); if (objectNode.getType() != Token.OBJECTLIT) { t.getCompiler().report(JSError.make(t.getSourceName(), callNode, OBJECTLIT_EXPECTED)); return null; } return new ObjectLiteralCast(typeNode.getQualifiedName(), typeNode.getNext()); } /** * {@inheritDoc} */ @Override public boolean isOptionalParameter(Node parameter) { return false; } /** * {@inheritDoc} */ @Override public boolean isVarArgsParameter(Node parameter) { return false; } /** * {@inheritDoc} */ @Override public boolean isPrivate(String name) { return false; } }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>() { return getBooleanProp(QUOTED_PROP); } /** * This should only be called for STRING nodes created in object lits. */ @Override public void setQuotedString() { putBooleanProp(QUOTED_PROP, true); } private String str; } private static class PropListItem implements Serializable { private static final long serialVersionUID = 1L; PropListItem next; int type; int intValue; Object objectValue; } public Node(int nodeType) { type = nodeType; parent = null; sourcePosition = -1; } public Node(int nodeType, Node child) { Preconditions.checkArgument(child.parent == null, "new child has existing parent"); Preconditions.checkArgument(child.next == null, "new child has existing sibling"); type = nodeType; parent = null; first = last = child; child.next = null; child.parent = this; sourcePosition = -1; } public Node(int nodeType, Node left, Node right) { Preconditions.checkArgument(left.parent == null, "first new child has existing parent"); Preconditions.checkArgument(left.next == null, "first new child has existing sibling"); Preconditions.checkArgument(right.parent == null, "second new child has existing parent"); Preconditions.checkArgument(right.next == null, "second new child has existing sibling"); type = nodeType; parent = null; first = left; last = right; left.next = right; left.parent = this; right.next = null; right.parent = this; sourcePosition = -1; } public Node(int nodeType, Node left, Node mid, Node right) { Preconditions.checkArgument(left.parent == null); Preconditions.checkArgument(left.next == null); Preconditions.checkArgument(mid.parent == null); Preconditions.checkArgument(mid.next == null); Preconditions.checkArgument(right.parent == null); Preconditions.checkArgument(right.next == null); type = nodeType; parent = null; first = left; last = right; left.next = mid; left.parent

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> = this; mid.next = right; mid.parent = this; right.next = null; right.parent = this; sourcePosition = -1; } public Node(int nodeType, Node left, Node mid, Node mid2, Node right) { Preconditions.checkArgument(left.parent == null); Preconditions.checkArgument(left.next == null); Preconditions.checkArgument(mid.parent == null); Preconditions.checkArgument(mid.next == null); Preconditions.checkArgument(mid2.parent == null); Preconditions.checkArgument(mid2.next == null); Preconditions.checkArgument(right.parent == null); Preconditions.checkArgument(right.next == null); type = nodeType; parent = null; first = left; last = right; left.next = mid; left.parent = this; mid.next = mid2; mid.parent = this; mid2.next = right; mid2.parent = this; right.next = null; right.parent = this; sourcePosition = -1; } public Node(int nodeType, int lineno, int charno) { type = nodeType; parent = null; sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node child, int lineno, int charno) { this(nodeType, child); sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node left, Node right, int lineno, int charno) { this(nodeType, left, right); sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node left, Node mid, Node right, int lineno, int charno) { this(nodeType, left, mid, right); sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node left, Node mid, Node mid2, Node right, int lineno, int charno) { this(nodeType, left, mid, mid2, right); sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node[] children, int lineno,

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> int charno) { this(nodeType, children); sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node[] children) { this.type = nodeType; parent = null; if (children.length != 0) { this.first = children[0]; this.last = children[children.length - 1]; for (int i = 1; i < children.length; i++) { if (null != children[i - 1].next) { // fail early on loops. implies same node in array twice throw new IllegalArgumentException("duplicate child"); } children[i - 1].next = children[i]; Preconditions.checkArgument(children[i - 1].parent == null); children[i - 1].parent = this; } Preconditions.checkArgument( children[children.length - 1].parent == null); children[children.length - 1].parent = this; if (null != this.last.next) { // fail early on loops. implies same node in array twice throw new IllegalArgumentException("duplicate child"); } } } public static Node newNumber(double number) { return new NumberNode(number); } public static Node newNumber(double number, int lineno, int charno) { return new NumberNode(number, lineno, charno); } public static Node newString(String str) { return new StringNode(Token.STRING, str); } public static Node newString(int type, String str) { return new StringNode(type, str); } public static Node newString(String str, int lineno, int charno) { return new StringNode(Token.STRING, str, lineno, charno); } public static Node newString(int type, String str, int lineno, int charno) { return new StringNode(type, str, lineno, charno); } public int getType() { return type; } public void setType(int type) { this.type = type; } public boolean hasChildren() { return first != null; } public Node getFirstChild() { return first; } public Node getLastChild

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>() { return last; } public Node getNext() { return next; } public Node getChildBefore(Node child) { if (child == first) return null; Node n = first; while (n.next != child) { n = n.next; if (n == null) throw new RuntimeException("node is not a child"); } return n; } public Node getChildAtIndex(int i) { Node n = first; while (i > 0) { n = n.next; i--; } return n; } public Node getLastSibling() { Node n = this; while (n.next != null) { n = n.next; } return n; } public void addChildToFront(Node child) { Preconditions.checkArgument(child.parent == null); Preconditions.checkArgument(child.next == null); child.parent = this; child.next = first; first = child; if (last == null) { last = child; } } public void addChildToBack(Node child) { Preconditions.checkArgument(child.parent == null); Preconditions.checkArgument(child.next == null); child.parent = this; child.next = null; if (last == null) { first = last = child; return; } last.next = child; last = child; } public void addChildrenToFront(Node children) { for (Node child = children; child != null; child = child.next) { Preconditions.checkArgument(child.parent == null); child.parent = this; } Node lastSib = children.getLastSibling(); lastSib.next = first; first = children; if (last == null) { last = lastSib; } } public void addChildrenToBack(Node children) { for (Node child = children; child != null; child = child.next) { // Hmmm... IRFactory doesn't remove before calling this. Preconditions.checkArgument(child.parent == null); child.parent = this; } if (last != null) { last.next = children; }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> last = children.getLastSibling(); if (first == null) { first = children; } } /** * Add 'child' before 'node'. */ public void addChildBefore(Node newChild, Node node) { Preconditions.checkArgument(node != null, "The existing child node of the parent should not be null."); Preconditions.checkArgument(newChild.next == null, "The new child node has siblings."); Preconditions.checkArgument(newChild.parent == null, "The new child node already has a parent."); if (first == node) { newChild.parent = this; newChild.next = first; first = newChild; return; } Node prev = getChildBefore(node); addChildAfter(newChild, prev); } /** * Add 'child' after 'node'. */ public void addChildAfter(Node newChild, Node node) { Preconditions.checkArgument(newChild.next == null, "The new child node has siblings."); Preconditions.checkArgument(newChild.parent == null, "The new child node already has a parent."); newChild.parent = this; newChild.next = node.next; node.next = newChild; if (last == node) { last = newChild; } } /** * Detach a child from its parent and siblings. */ public void removeChild(Node child) { Node prev = getChildBefore(child); if (prev == null) first = first.next; else prev.next = child.next; if (child == last) last = prev; child.next = null; child.parent = null; } /** * Detaches child from Node and replaces it with newChild. */ public void replaceChild(Node child, Node newChild) { Preconditions.checkArgument(newChild.next == null, "The new child node has siblings."); Preconditions.checkArgument(newChild.parent == null, "The new child node already has a parent."); // Copy over important information. newChild.copyInformationFrom(child); newChild.next = child.next; newChild.parent = this; if (child ==

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> first) { first = newChild; } else { Node prev = getChildBefore(child); prev.next = newChild; } if (child == last) last = newChild; child.next = null; child.parent = null; } public void replaceChildAfter(Node prevChild, Node newChild) { Preconditions.checkArgument(prevChild.parent == this, "prev is not a child of this node."); Preconditions.checkArgument(newChild.next == null, "The new child node has siblings."); Preconditions.checkArgument(newChild.parent == null, "The new child node already has a parent."); // Copy over important information. newChild.copyInformationFrom(prevChild); Node child = prevChild.next; newChild.next = child.next; newChild.parent = this; prevChild.next = newChild; if (child == last) last = newChild; child.next = null; child.parent = null; } private PropListItem lookupProperty(int propType) { PropListItem x = propListHead; while (x != null && propType != x.type) { x = x.next; } return x; } private PropListItem ensureProperty(int propType) { PropListItem item = lookupProperty(propType); if (item == null) { item = new PropListItem(); item.type = propType; item.next = propListHead; propListHead = item; } return item; } public void removeProp(int propType) { PropListItem x = propListHead; if (x != null) { PropListItem prev = null; while (x.type != propType) { prev = x; x = x.next; if (x == null) { return; } } if (prev == null) { propListHead = x.next; } else { prev.next = x.next; } } } public Object getProp(int propType) { PropListItem item = lookupProperty(propType); if (item == null) { return null; } return item.objectValue; } public

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>Double(double s) throws UnsupportedOperationException { if (this.getType() == Token.NUMBER) { throw new IllegalStateException( "Number node not created with Node.newNumber"); } else { throw new UnsupportedOperationException( this + " is not a string node"); } } /** Can only be called when node has String context. */ public String getString() throws UnsupportedOperationException { if (this.getType() == Token.STRING) { throw new IllegalStateException( "String node not created with Node.newString"); } else { throw new UnsupportedOperationException( this + " is not a string node"); } } /** Can only be called when node has String context. */ public void setString(String s) throws UnsupportedOperationException { if (this.getType() == Token.STRING) { throw new IllegalStateException( "String node not created with Node.newString"); } else { throw new UnsupportedOperationException( this + " is not a string node"); } } @Override public String toString() { return toString(true, true, true); } public String toString( boolean printSource, boolean printAnnotations, boolean printType) { if (Token.printTrees) { StringBuilder sb = new StringBuilder(); toString(sb, printSource, printAnnotations, printType); return sb.toString(); } return String.valueOf(type); } private void toString( StringBuilder sb, boolean printSource, boolean printAnnotations, boolean printType) { if (Token.printTrees) { sb.append(Token.name(type)); if (this instanceof StringNode) { sb.append(' '); sb.append(getString()); } else if (type == Token.FUNCTION) { sb.append(' '); sb.append(first.getString()); } else if (this instanceof ScriptOrFnNode) { ScriptOrFnNode sof = (ScriptOrFnNode)this; if (this instanceof FunctionNode) { FunctionNode fn = (FunctionNode)this; sb.append(' '); sb.append(fn.getFunctionName()); } if (printSource) { sb.append(" [source name: "); sb.append(sof.getSourceName()); sb.append("] [encoded source length: "); sb.append

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> Node.children is in for // loops, this branch is extremely unlikely. return (new SiblingNodeIterable(start)).iterator(); } } public boolean hasNext() { return current != null; } public Node next() { if (current == null) { throw new NoSuchElementException(); } try { return current; } finally { current = current.getNext(); } } public void remove() { throw new UnsupportedOperationException(); } } //========================================================================== // Accessors public Node getParent() { return parent; } /** * Gets the ancestor node relative to this. * @param level 0 = this, 1 = the parent, etc. */ public Node getAncestor(int level) { Preconditions.checkArgument(level >= 0); Node node = this; while(node != null && level-- > 0) { node = node.getParent(); } return node; } /** * Iterates all of the node's ancestors excluding itself. */ public AncestorIterable getAncestors() { return new AncestorIterable(this.getParent()); } /** * Iterator to go up the ancestor tree. */ public static class AncestorIterable implements Iterable<Node> { private Node cur; /** * @param cur The node to start. */ AncestorIterable(Node cur) { this.cur = cur; } public Iterator<Node> iterator() { return new Iterator<Node>() { public boolean hasNext() { return cur != null; } public Node next() { if (!hasNext()) throw new NoSuchElementException(); Node n = cur; cur = cur.getParent(); return n; } public void remove() { throw new UnsupportedOperationException(); } }; } } /** * Check for one child more efficiently than by iterating over all the * children as is done with Node.getChildCount(). * @return Whether the node has exactly one child. */ public boolean hasOneChild() { return first != null && first == last; } /** * Check for more than one child more efficiently than by iterating over all * the children as is done with Node.getChildCount(). * @return Whether the node more than

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>NAME: return "typeofname"; case Token.THISFN: return "thisfn"; case Token.SEMI: return "semi"; case Token.LB: return "lb"; case Token.RB: return "rb"; case Token.LC: return "lc"; case Token.RC: return "rc"; case Token.LP: return "lp"; case Token.RP: return "rp"; case Token.COMMA: return "comma"; case Token.ASSIGN: return "assign"; case Token.ASSIGN_BITOR: return "assign_bitor"; case Token.ASSIGN_BITXOR: return "assign_bitxor"; case Token.ASSIGN_BITAND: return "assign_bitand"; case Token.ASSIGN_LSH: return "assign_lsh"; case Token.ASSIGN_RSH: return "assign_rsh"; case Token.ASSIGN_URSH: return "assign_ursh"; case Token.ASSIGN_ADD: return "assign_add"; case Token.ASSIGN_SUB: return "assign_sub"; case Token.ASSIGN_MUL: return "assign_mul"; case Token.ASSIGN_DIV: return "assign_div"; case Token.ASSIGN_MOD: return "assign_mod"; case Token.HOOK: return "hook"; case Token.COLON: return "colon"; case Token.OR: return "or"; case Token.AND: return "and"; case Token.INC: return "inc"; case Token.DEC: return "dec"; case Token.DOT: return "dot"; case Token.FUNCTION: return "function"; case Token.EXPORT: return "export"; case Token.IMPORT: return "import"; case Token.IF: return "if"; case Token.ELSE: return "else"; case Token.SWITCH: return "switch"; case Token.CASE: return "case"; case Token.DEFAULT: return "default"; case Token.WHILE: return "while"; case Token.DO: return "do"; case Token.FOR: return "for"; case Token.BREAK: return "break";

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> node.getIntProp(INCRDECR_PROP, 0); if (post1 != post2) return false; } else if (type == Token.STRING) { int quoted1 = this.getIntProp(QUOTED_PROP, 0); int quoted2 = node.getIntProp(QUOTED_PROP, 0); if (quoted1 != quoted2) return false; } return true; } public boolean hasSideEffects() { switch (type) { case Token.EXPR_VOID: case Token.COMMA: if (last != null) return last.hasSideEffects(); else return true; case Token.HOOK: if (first == null || first.next == null || first.next.next == null) Kit.codeBug(); return first.next.hasSideEffects() && first.next.next.hasSideEffects(); case Token.ERROR: // Avoid cascaded error messages case Token.EXPR_RESULT: case Token.ASSIGN: case Token.ASSIGN_ADD: case Token.ASSIGN_SUB: case Token.ASSIGN_MUL: case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: case Token.ASSIGN_BITOR: case Token.ASSIGN_BITXOR: case Token.ASSIGN_BITAND: case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.ASSIGN_URSH: case Token.ENTERWITH: case Token.LEAVEWITH: case Token.RETURN: case Token.GOTO: case Token.IFEQ: case Token.IFNE: case Token.NEW: case Token.DELPROP: case Token.SETNAME: case Token.SETPROP: case Token.SETELEM: case Token.CALL: case Token.THROW: case Token.RETHROW: case Token.SETVAR: case Token.CATCH_SCOPE: case Token.RETURN_RESULT: case Token.SET_REF: case Token.DEL_REF: case Token.REF_CALL: case Token.TRY: case Token.SEMI: case Token.INC: case Token.DEC: case Token.EXPORT: case Token.IMPORT: case Token.IF

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>scopedQualifiedName() { switch (getType()) { case Token.NAME: return true; case Token.GETPROP: return getFirstChild().isUnscopedQualifiedName(); default: return false; } } //========================================================================== // Mutators /** * Removes this node from its parent. Equivalent to: * node.getParent().removeChild(); */ public Node detachFromParent() { Preconditions.checkState(parent != null); parent.removeChild(this); return this; } /** * Removes the first child of Node. Equivalent to: * node.removeChild(node.getFirstChild()); * @return The removed Node. */ public Node removeFirstChild() { Node child = first; if (child != null) { removeChild(child); } return child; } /** * @return A Node that is the head of the list of children. */ public Node removeChildren() { Node children = first; for (Node child = first; child != null; child = child.getNext()) { child.parent = null; } first = null; last = null; return children; } /** * Removes all children from this node and isolates the children from each * other. */ public void detachChildren() { for (Node child = first; child != null; ) { Node nextChild = child.getNext(); child.parent = null; child.next = null; child = nextChild; } first = null; last = null; } public Node removeChildAfter(Node prev) { Preconditions.checkArgument(prev.parent == this, "prev is not a child of this node."); Preconditions.checkArgument(prev.next != null, "no next sibling."); Node child = prev.next; prev.next = child.next; if (child == last) last = prev; child.next = null; child.parent = null; return child; } /** * @return A detached clone of the Node, specifically excluding its * children. */ public Node cloneNode() { Node result; try { result = (Node) super.clone(); result.next = null;

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> node is an optional argument node. This * method is meaningful only on {@link Token#NAME} nodes * used to define a {@link Token#FUNCTION}'s argument list. */ public void setOptionalArg(boolean optionalArg) { putBooleanProp(OPT_ARG_NAME, optionalArg); } /** * Returns whether this node is an optional argument node. This * method's return value is meaningful only on {@link Token#NAME} nodes * used to define a {@link Token#FUNCTION}'s argument list. */ public boolean isOptionalArg() { return getBooleanProp(OPT_ARG_NAME); } /** * Sets whether this is a synthetic block that should not be considered * a real source block. */ public void setIsSyntheticBlock(boolean val) { putBooleanProp(SYNTHETIC_BLOCK_PROP, val); } /** * Returns whether this is a synthetic block that should not be considered * a real source block. */ public boolean isSyntheticBlock() { return getBooleanProp(SYNTHETIC_BLOCK_PROP); } /** * Sets the ES5 directives on this node. */ public void setDirectives(Set<String> val) { putProp(DIRECTIVES, val); } /** * Returns the set of ES5 directives for this node. */ @SuppressWarnings("unchecked") public Set<String> getDirectives() { return (Set<String>) getProp(DIRECTIVES); } /** * Sets whether this is a synthetic block that should not be considered * a real source block. */ public void setWasEmptyNode(boolean val) { putBooleanProp(EMPTY_BLOCK, val); } /** * Returns whether this is a synthetic block that should not be considered * a real source block. */ public boolean wasEmptyNode() { return getBooleanProp(EMPTY_BLOCK); } /** * Marks this function or constructor call node as having no side effects. * This property is only meaningful for {@link Token#CALL} and * {@link Token#NEW} nodes. */ public void setIsNoSideEffectsCall() { Preconditions.checkArgument( getType() == Token.CALL || getType() == Token.NEW,

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> constructor; InstanceObjectType(JSTypeRegistry registry, FunctionType constructor) { this(registry, constructor, false); } InstanceObjectType(JSTypeRegistry registry, FunctionType constructor, boolean isNativeType) { super(registry, null, null, isNativeType); Preconditions.checkNotNull(constructor); this.constructor = constructor; } @Override public String getReferenceName() { return getConstructor().getReferenceName(); } @Override public boolean hasReferenceName() { return getConstructor().hasReferenceName(); } @Override public ObjectType getImplicitPrototype() { return getConstructor().getPrototype(); } @Override public FunctionType getConstructor() { return constructor; } @Override boolean defineProperty(String name, JSType type, boolean inferred, boolean inExterns) { ObjectType proto = getImplicitPrototype(); if (proto != null && proto.hasOwnDeclaredProperty(name)) { return false; } return super.defineProperty(name, type, inferred, inExterns); } @Override public String toString() { return constructor.getReferenceName(); } @Override boolean isTheObjectType() { return getConstructor().isNative() && "Object".equals(getReferenceName()); } @Override public boolean isInstanceType() { return true; } @Override public boolean isArrayType() { return getConstructor().isNative() && "Array".equals(getReferenceName()); } @Override public boolean isStringObjectType() { return getConstructor().isNative() && "String".equals(getReferenceName()); } @Override public boolean isBooleanObjectType() { return getConstructor().isNative() && "Boolean".equals(getReferenceName()); } @Override public boolean isNumberObjectType() { return getConstructor().isNative() && "Number".equals(getReferenceName()); } @Override public boolean isDateType() { return getConstructor().isNative() && "Date".equals(getReferenceName()); } @Override public boolean isRegexpType() { return getConstructor().isNative() && "RegExp".equals(getReferenceName()); } @Override public boolean isNominalType() { return hasReferenceName(); } @Override public boolean equals(Object that) { if

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> setWarningLevels(CompilerOptions options, List<String> diagnosticGroups, CheckLevel level) { for (String name : diagnosticGroups) { DiagnosticGroup group = forName(name); Preconditions.checkNotNull(group, "No warning class for name: " + name); options.setWarningLevel(group, level); } } }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> map and connect: * foo() -> bar() * bar() -> END */ private final Multimap<Node, Node> finallyMap = HashMultimap.create(); /** * Constructor. * * @param compiler Compiler instance. * @param shouldTraverseFunctions Whether functions should be traversed (true * by default). */ ControlFlowAnalysis(AbstractCompiler compiler, boolean shouldTraverseFunctions) { this.compiler = compiler; this.shouldTraverseFunctions = shouldTraverseFunctions; } ControlFlowGraph<Node> getCfg() { return cfg; } @Override public void process(Node externs, Node root) { this.root = root; astPositionCounter = 0; astPosition = Maps.newHashMap(); nodePriorities = Maps.newHashMap(); cfg = new AstControlFlowGraph(computeFallThrough(root), nodePriorities); NodeTraversal.traverse(compiler, root, this); astPosition.put(null, ++astPositionCounter); // the implicit return is last. // Now, generate the priority of nodes by doing a depth-first // search on the CFG. priorityCounter = 0; DiGraphNode<Node, Branch> entry = cfg.getEntry(); prioritizeFromEntryNode(entry); if (shouldTraverseFunctions) { // If we're traversing inner functions, we need to rank the // priority of them too. for (DiGraphNode<Node, Branch> candidate : cfg.getDirectedGraphNodes()) { Node value = candidate.getValue(); if (value != null && value.getType() == Token.FUNCTION) { Preconditions.checkState( !nodePriorities.containsKey(candidate) || candidate == entry); prioritizeFromEntryNode(candidate); } } } // At this point, all reachable nodes have been given a priority, but // unreachable nodes have not been given a priority. Put them last. // Presumably, it doesn't really matter what priority they get, since // this shouldn't happen in real code. for (DiGraphNode<Node, Branch> candidate : cfg.getDirectedGraphNodes()) { if (!nodePriorities.containsKey(candidate)) { nodePriorities.put(candidate, ++priorityCounter);

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> } } // Again, the implicit return node is always last. nodePriorities.put(cfg.getImplicitReturn(), ++priorityCounter); } /** * Given an entry node, find all the nodes reachable from that node * and prioritize them. */ private void prioritizeFromEntryNode(DiGraphNode<Node, Branch> entry) { PriorityQueue<DiGraphNode<Node, Branch>> worklist = new PriorityQueue<DiGraphNode<Node, Branch>>(10, priorityComparator); worklist.add(entry); while (!worklist.isEmpty()) { DiGraphNode<Node, Branch> current = worklist.remove(); if (nodePriorities.containsKey(current)) { continue; } nodePriorities.put(current, ++priorityCounter); List<DiGraphNode<Node, Branch>> successors = cfg.getDirectedSuccNodes(current); for (DiGraphNode<Node, Branch> candidate : successors) { worklist.add(candidate); } } } @Override public boolean shouldTraverse( NodeTraversal nodeTraversal, Node n, Node parent) { astPosition.put(n, astPositionCounter++); switch (n.getType()) { case Token.FUNCTION: if (shouldTraverseFunctions || n == cfg.getEntry().getValue()) { exceptionHandler.push(n); return true; } return false; case Token.TRY: exceptionHandler.push(n); return true; } /* * We are going to stop the traversal depending on what the node's parent * is. * * We are only interested in adding edges between nodes that change control * flow. The most obvious ones are loops and IF-ELSE's. A statement * transfers control to its next sibling. * * In case of an expression tree, there is no control flow within the tree * even when there are short circuited operators and conditionals. When we * are doing data flow analysis, we will simply synthesize lattices up the * expression tree by finding the meet at each expression node. * * For example: within a Token.SWITCH, the expression in question does not * change the control flow and need not to be considered. */ if (parent !=

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> null) { switch (parent.getType()) { case Token.FOR: // Only traverse the body of the for loop. return n == parent.getLastChild(); // Skip the conditions. case Token.IF: case Token.WHILE: case Token.WITH: return n != parent.getFirstChild(); case Token.DO: return n != parent.getFirstChild().getNext(); // Only traverse the body of the cases case Token.SWITCH: case Token.CASE: case Token.CATCH: case Token.LABEL: return n != parent.getFirstChild(); case Token.FUNCTION: return n == parent.getFirstChild().getNext().getNext(); case Token.CONTINUE: case Token.BREAK: case Token.EXPR_RESULT: case Token.VAR: case Token.RETURN: case Token.THROW: return false; case Token.TRY: /* Just before we are about to visit the second child of the TRY node, * we know that we will be visiting either the CATCH or the FINALLY. * In other words, we know that the post order traversal of the TRY * block has been finished, no more exceptions can be caught by the * handler at this TRY block and should be taken out of the stack. */ if (n == parent.getFirstChild().getNext()) { Preconditions.checkState(exceptionHandler.peek() == parent); exceptionHandler.pop(); } } } return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.IF: handleIf(n); return; case Token.WHILE: handleWhile(n); return; case Token.DO: handleDo(n); return; case Token.FOR: handleFor(n); return; case Token.SWITCH: handleSwitch(n); return; case Token.CASE: handleCase(n); return; case Token.DEFAULT: handleDefault(n); return; case Token.BLOCK: case Token.SCRIPT: handleStmtList(n); return; case Token.FUNCTION: handleFunction(n); return; case Token.EXPR_RESULT: handle

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>, Branch.UNCOND, computeFollowNode(node)); } } connectToPossibleExceptionHandler(node, node.getFirstChild()); } private void handleCase(Node node) { // Case is a bit tricky....First it goes into the body if condition is true. createEdge(node, Branch.ON_TRUE, node.getFirstChild().getNext()); // Look for the next CASE, skipping over DEFAULT. Node next = getNextSiblingOfType(node.getNext(), Token.CASE); if (next != null) { // Found a CASE Preconditions.checkState(next.getType() == Token.CASE); createEdge(node, Branch.ON_FALSE, next); } else { // No more CASE found, go back and search for a DEFAULT. Node parent = node.getParent(); Node deflt = getNextSiblingOfType( parent.getFirstChild().getNext(), Token.DEFAULT); if (deflt != null) { // Has a DEFAULT createEdge(node, Branch.ON_FALSE, deflt); } else { // No DEFAULT found, go to the follow of the SWITCH. createEdge(node, Branch.ON_FALSE, computeFollowNode(node)); } } connectToPossibleExceptionHandler(node, node.getFirstChild()); } private void handleDefault(Node node) { // Directly goes to the body. It should not transfer to the next case. createEdge(node, Branch.UNCOND, node.getFirstChild()); } private void handleWith(Node node) { // Directly goes to the body. It should not transfer to the next case. createEdge(node, Branch.UNCOND, node.getLastChild()); connectToPossibleExceptionHandler(node, node.getFirstChild()); } private void handleStmtList(Node node) { Node parent = node.getParent(); // Special case, don't add a block of empty CATCH block to the graph. if (node.getType() == Token.BLOCK && parent != null && parent.getType() == Token.TRY && NodeUtil.getCatchBlock(parent) == node && !NodeUtil.hasCatchHandler(node)) { return; } // A block transfer control to its first child if it is not empty. Node child = node.getFirstChild(); // Function declarations are skipped since

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> control doesn't go into that // function (unless it is called) while (child != null && child.getType() == Token.FUNCTION) { child = child.getNext(); } if (child != null) { createEdge(node, Branch.UNCOND, computeFallThrough(child)); } else { createEdge(node, Branch.UNCOND, computeFollowNode(node)); } // Synthetic blocks if (parent != null) { switch (parent.getType()) { case Token.DEFAULT: case Token.CASE: case Token.TRY: break; default: if (node.getType() == Token.BLOCK && node.isSyntheticBlock()) { Node next = node.getLastChild(); if (next != null) { createEdge(node, Branch.SYN_BLOCK, computeFallThrough(next)); } } break; } } } private void handleFunction(Node node) { // A block transfer control to its first child if it is not empty. Preconditions.checkState(node.getChildCount() >= 3); createEdge(node, Branch.UNCOND, computeFallThrough(node.getFirstChild().getNext().getNext())); Preconditions.checkState(exceptionHandler.peek() == node); exceptionHandler.pop(); } private void handleExpr(Node node) { createEdge(node, Branch.UNCOND, computeFollowNode(node)); connectToPossibleExceptionHandler(node, node); } private void handleThrow(Node node) { connectToPossibleExceptionHandler(node, node); } private void handleTry(Node node) { createEdge(node, Branch.UNCOND, node.getFirstChild()); } private void handleCatch(Node node) { createEdge(node, Branch.UNCOND, node.getLastChild()); } private void handleBreak(Node node) { String label = null; // See if it is a break with label. if (node.hasChildren()) { label = node.getFirstChild().getString(); } Node cur; Node lastJump; Node parent = node.getParent(); /* * Continuously look up the ancestor tree for the BREAK target or the target * with the corresponding label and connect to it. If along the path we *

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> discover a FINALLY, we will connect the BREAK to that FINALLY. From then * on, we will just record the control flow changes in the finallyMap. This * is due to the fact that we need to connect any node that leaves its own * FINALLY block to the outer FINALLY or the BREAK's target but those nodes * are not known yet due to the way we traverse the nodes. */ for (cur = node, lastJump = node; !isBreakTarget(cur, parent, label); cur = parent, parent = parent.getParent()) { if (cur.getType() == Token.TRY && NodeUtil.hasFinally(cur)) { if (lastJump == node) { createEdge(lastJump, Branch.UNCOND, computeFallThrough( cur.getLastChild())); } else { finallyMap.put(lastJump, computeFallThrough(cur.getLastChild())); } lastJump = cur; } Preconditions.checkState(parent != null, "Cannot find break target."); } if (lastJump == node) { createEdge(lastJump, Branch.UNCOND, computeFollowNode(cur)); } else { finallyMap.put(lastJump, computeFollowNode(cur)); } } private void handleContinue(Node node) { String label = null; if (node.hasChildren()) { label = node.getFirstChild().getString(); } Node cur; Node lastJump; // Similar to handBreak's logic with a few minor variation. Node parent = node.getParent(); for (cur = node, lastJump = node; !isContinueTarget(cur, parent, label); cur = parent, parent = parent.getParent()) { if (cur.getType() == Token.TRY && NodeUtil.hasFinally(cur)) { if (lastJump == node) { createEdge(lastJump, Branch.UNCOND, cur.getLastChild()); } else { finallyMap.put(lastJump, computeFallThrough(cur.getLastChild())); } lastJump = cur; } Preconditions.checkState(parent != null, "Cannot find continue target."); } Node iter = cur; if (cur.getChildCount() == 4) { iter = cur.getFirstChild().getNext().getNext

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>3. If the node is a return statement, we should also transfer control * back to the caller of the function. * * 4. If the node is root then we have reached the end of what we have been * asked to traverse. * * In all cases we should transfer control to a "symbolic return" node. * This will make life easier for DFAs. */ Node parent = node.getParent(); if (parent == null || parent.getType() == Token.FUNCTION || node == root) { return null; } // If we are just before a IF/WHILE/DO/FOR: switch (parent.getType()) { // The follow() of any of the path from IF would be what follows IF. case Token.IF: return computeFollowNode(fromNode, parent); case Token.CASE: case Token.DEFAULT: // After the body of a CASE, the control goes to the body of the next // case, without having to go to the case condition. if (parent.getNext() != null) { if (parent.getNext().getType() == Token.CASE) { return parent.getNext().getFirstChild().getNext(); } else if (parent.getNext().getType() == Token.DEFAULT) { return parent.getNext().getFirstChild(); } else { Preconditions.checkState(false, "Not reachable"); } } else { return computeFollowNode(fromNode, parent); } break; case Token.FOR: if (NodeUtil.isForIn(parent)) { return parent; } else { return parent.getFirstChild().getNext().getNext(); } case Token.WHILE: case Token.DO: return parent; case Token.TRY: // If we are coming out of the TRY block... if (parent.getFirstChild() == node) { if (NodeUtil.hasFinally(parent)) { // and have FINALLY block. return computeFallThrough(parent.getLastChild()); } else { // and have no FINALLY. return computeFollowNode(fromNode, parent); } // CATCH block. } else if (NodeUtil.getCatchBlock(parent) == node){ if (NodeUtil.hasFinally(parent)) { // and have FINALLY block.

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> return computeFallThrough(node.getNext()); } else { return computeFollowNode(fromNode, parent); } // If we are coming out of the FINALLY block... } else if (parent.getLastChild() == node){ for (Node finallyNode : finallyMap.get(parent)) { createEdge(fromNode, Branch.UNCOND, finallyNode); } return computeFollowNode(fromNode, parent); } } // Now that we are done with the special cases follow should be its // immediate sibling, unless its sibling is a function Node nextSibling = node.getNext(); // Skip function declarations because control doesn't get pass into it. while (nextSibling != null && nextSibling.getType() == Token.FUNCTION) { nextSibling = nextSibling.getNext(); } if (nextSibling != null) { return computeFallThrough(nextSibling); } else { // If there are no more siblings, control is transfered up the AST. return computeFollowNode(fromNode, parent); } } /** * Computes the destination node of n when we want to fallthough into the * subtree of n. We don't always create a CFG edge into n itself because of * DOs and FORs. */ private static Node computeFallThrough(Node n) { switch (n.getType()) { case Token.DO: return computeFallThrough(n.getFirstChild()); case Token.FOR: if (NodeUtil.isForIn(n)) { return n; } return computeFallThrough(n.getFirstChild()); case Token.LABEL: return computeFallThrough(n.getLastChild()); default: return n; } } /** * Connects the two nodes in the control flow graph. * * @param fromNode Source. * @param toNode Destination. */ private void createEdge(Node fromNode, ControlFlowGraph.Branch branch, Node toNode) { cfg.createNode(fromNode); cfg.createNode(toNode); cfg.connectIfNotFound(fromNode, branch, toNode); } /** * Connects cfgNode to the proper CATCH block if target subtree might throw * an exception. If there are FINALLY blocks reached before a CATCH, it will

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> * make the corresponding entry in finallyMap. */ private void connectToPossibleExceptionHandler(Node cfgNode, Node target) { if (mayThrowException(target) && !exceptionHandler.isEmpty()) { Node lastJump = cfgNode; for (Node handler : exceptionHandler) { if (NodeUtil.isFunction(handler)) { return; } Preconditions.checkState(handler.getType() == Token.TRY); Node catchBlock = NodeUtil.getCatchBlock(handler); if (!NodeUtil.hasCatchHandler(catchBlock)) { // No catch but a FINALLY. if (lastJump == cfgNode) { createEdge(cfgNode, Branch.ON_EX, handler.getLastChild()); } else { finallyMap.put(lastJump, handler.getLastChild()); } } else { // Has a catch. if (lastJump == cfgNode) { createEdge(cfgNode, Branch.ON_EX, catchBlock); return; } else { finallyMap.put(lastJump, catchBlock); } } lastJump = handler; } } } /** * Get the next sibling (including itself) of one of the given types. */ private static Node getNextSiblingOfType(Node first, int ... types) { for (Node c = first; c != null; c = c.getNext()) { for (int type : types) { if (c.getType() == type) { return c; } } } return null; } /** * Checks if target is actually the break target of labeled continue. The * label can be null if it is an unlabeled break. */ private static boolean isBreakTarget( Node target, Node parent, String label) { return isBreakStructure(target, label != null) && matchLabel(parent, label); } /** * Checks if target is actually the continue target of labeled continue. The * label can be null if it is an unlabeled continue. */ private static boolean isContinueTarget( Node target, Node parent, String label) { return isContinueStructure(target) && matchLabel(parent, label); } /** * Check if label is actually referencing the target control structure. If * label is null, it always returns

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> true. */ private static boolean matchLabel(Node target, String label) { if (label == null) { return true; } while (target.getType() == Token.LABEL) { if (target.getFirstChild().getString().equals(label)) { return true; } target = target.getParent(); } return false; } /** * Determines if the subtree might throw an exception. */ private static boolean mayThrowException(Node n) { switch (n.getType()) { case Token.CALL: case Token.GETPROP: case Token.GETELEM: case Token.THROW: case Token.NEW: case Token.ASSIGN: case Token.INC: case Token.DEC: case Token.INSTANCEOF: return true; case Token.FUNCTION: return false; } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(c) && mayThrowException(c)) { return true; } } return false; } /** * Determines whether the given node can be terminated with a BREAK node. */ static boolean isBreakStructure(Node n, boolean labeled) { switch (n.getType()) { case Token.FOR: case Token.DO: case Token.WHILE: case Token.SWITCH: return true; case Token.BLOCK: case Token.IF: case Token.TRY: return labeled; default: return false; } } /** * Determines whether the given node can be advanced with a CONTINUE node. */ static boolean isContinueStructure(Node n) { switch (n.getType()) { case Token.FOR: case Token.DO: case Token.WHILE: return true; default: return false; } } /** * A {@link ControlFlowGraph} which provides a node comparator based on the * pre-order traversal of the AST. */ private static class AstControlFlowGraph extends ControlFlowGraph<Node> { private final Map<DiGraphNode<Node, Branch>, Integer> priorities; /** * Constructor. * @param entry The entry node. * @param

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> priorities The map from nodes to position in the AST (to be * filled by the {@link ControlFlowAnalysis#shouldTraverse}). */ private AstControlFlowGraph(Node entry, Map<DiGraphNode<Node, Branch>, Integer> priorities) { super(entry); this.priorities = priorities; } @Override /** * Returns a node comparator based on the pre-order traversal of the AST. * @param isForward x 'before' y in the pre-order traversal implies * x 'less than' y (if true) and x 'greater than' y (if false). */ public Comparator<DiGraphNode<Node, Branch>> getOptionalNodeComparator( boolean isForward) { if (isForward) { return new Comparator<DiGraphNode<Node, Branch>>() { @Override public int compare( DiGraphNode<Node, Branch> n1, DiGraphNode<Node, Branch> n2) { return getPosition(n1) - getPosition(n2); } }; } else { return new Comparator<DiGraphNode<Node, Branch>>() { @Override public int compare( DiGraphNode<Node, Branch> n1, DiGraphNode<Node, Branch> n2) { return getPosition(n2) - getPosition(n1); } }; } } /** * Gets the pre-order traversal position of the given node. * @return An arbitrary counter used for comparing positions. */ private int getPosition(DiGraphNode<Node, Branch> n) { Integer priority = priorities.get(n); Preconditions.checkNotNull(priority); return priority; } } }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> instanceof ObjectType) && !(enumTypeNames.contains(n.getString()))) { Node typeList = n.getFirstChild(); if (typeList != null && ("Array".equals(n.getString()) || "Object".equals(n.getString()))) { JSType parameterType = createFromTypeNodes( typeList.getLastChild(), sourceName, scope); namedType = new ParameterizedType( this, (ObjectType) namedType, parameterType); if (typeList.hasMoreThanOneChild()) { JSType indexType = createFromTypeNodes( typeList.getFirstChild(), sourceName, scope); namedType = new IndexedType( this, (ObjectType) namedType, indexType); } } return createNullableType(namedType); } else { return namedType; } case Token.FUNCTION: ObjectType thisType = null; Node current = n.getFirstChild(); if (current.getType() == Token.THIS) { Node thisNode = current.getFirstChild(); thisType = ObjectType.cast( createFromTypeNodes(thisNode, sourceName, scope) .restrictByNotNullOrUndefined()); if (thisType == null) { reporter.warning( ScriptRuntime.getMessage0("msg.jsdoc.function.thisnotobject"), sourceName, thisNode.getLineno(), "", thisNode.getCharno()); } current = current.getNext(); } FunctionParamBuilder paramBuilder = new FunctionParamBuilder(this); if (current.getType() == Token.LP) { Node args = current.getFirstChild(); for (Node arg = current.getFirstChild(); arg != null; arg = arg.getNext()) { if (arg.getType() == Token.ELLIPSIS) { if (arg.getChildCount() == 0) { paramBuilder.addVarArgs(getNativeType(UNKNOWN_TYPE)); } else { paramBuilder.addVarArgs( createFromTypeNodes( arg.getFirstChild(), sourceName, scope)); } } else { JSType type = createFromTypeNodes(arg, sourceName, scope); if (arg.getType() == Token.EQUALS) { boolean addSuccess = paramBuilder.addOptionalParams(type); if (!addSuccess

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>EnteringNewCfgNode(n); } } /** * @return True if n should be represented by a new CFG node in the control * flow graph. */ public static boolean isEnteringNewCfgNode(Node n) { Node parent = n.getParent(); switch (parent.getType()) { case Token.BLOCK: case Token.SCRIPT: case Token.TRY: case Token.FINALLY: return true; case Token.FUNCTION: // A function node represents the start of a function where the name // is bleed into the local scope and parameters has been assigned // to the formal argument names. The node includes the name of the // function and the LP list since we assume the whole set up process // is atomic without change in control flow. The next change of // control is going into the function's body represent by the second // child. return n != parent.getFirstChild().getNext(); case Token.WHILE: case Token.DO: case Token.IF: // Theses control structure is represented by its node that holds the // condition. Each of them is a branch node based on its condition. return NodeUtil.getConditionExpression(parent) != n; case Token.FOR: // The FOR(;;) node differs from other control structure in that // it has a initialization and a increment statement. Those // two statements have its corresponding CFG nodes to represent them. // The FOR node represents the condition check for each iteration. // That way the following: // for(var x = 0; x < 10; x++) { } has a graph that is isomorphic to // var x = 0; while(x<10) { x++; } if (NodeUtil.isForIn(parent)) { return n == parent.getLastChild(); } else { return NodeUtil.getConditionExpression(parent) != n; } case Token.SWITCH: case Token.CASE: case Token.CATCH: case Token.WITH: return n != parent.getFirstChild(); default: return false; } } }

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>/* * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Rhino code, released * May 6, 1999. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1997-1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Norris Boyd * Roger Lawrence * * Alternatively, the contents of this file may be used under the terms of * the GNU General Public License Version 2 or later (the "GPL"), in which * case the provisions of the GPL are applicable instead of those above. If * you wish to allow use of your version of this file only under the terms of * the GPL and not to allow others to use your version of this file under the * MPL, indicate your decision by deleting the provisions above and replacing * them with the notice and other provisions required by the GPL. If you do * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. * * ***** END LICENSE BLOCK ***** */ package com.google.javascript.rhino; public class FunctionNode extends ScriptOrFnNode { private static final long serialVersionUID = 1L; public FunctionNode(String name) { super(Token.FUNCTION); functionName = name; } public FunctionNode(String name, int lineno, int charno) { super(Token.FUNCTION, lineno, charno); functionName = name; } public String getFunctionName() { return

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> null; } private abstract class AbstractScopeBuilder implements NodeTraversal.Callback { /** * The scope that we're builidng. */ final Scope scope; /** * The current source file that we're in. */ private String sourceName = null; private AbstractScopeBuilder(Scope scope) { this.scope = scope; } @Override public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { if (n.getType() == Token.FUNCTION || n.getType() == Token.SCRIPT) { sourceName = (String) n.getProp(Node.SOURCENAME_PROP); } // We do want to traverse the name of a named function, but we don't // want to traverse the arguments or body. return parent == null || parent.getType() != Token.FUNCTION || n == parent.getFirstChild() || parent == scope.getRootNode(); } @Override public abstract void visit(NodeTraversal t, Node n, Node parent); /** * Returns the type specified in a JSDoc annotation near a GETPROP or NAME. * * Extracts type information from either the {@code @type} tag or from * the {@code @return} and {@code @param} tags. */ JSType getDeclaredTypeInAnnotation( NodeTraversal t, Node node, JSDocInfo info) { return getDeclaredTypeInAnnotation(t.getSourceName(), node, info); } JSType getDeclaredTypeInAnnotation(String sourceName, Node node, JSDocInfo info) { JSType jsType = null; Node objNode = node.getType() == Token.GETPROP ? node.getFirstChild() : null; if (info != null) { if (info.hasType()) { jsType = info.getType().evaluate(scope); } else if (FunctionTypeBuilder.isFunctionTypeDeclaration(info)) { String fnName = node.getQualifiedName(); // constructors are often handled separately. if (info.isConstructor() && typeRegistry.getType(fnName) != null) { return null; } FunctionTypeBuilder builder = new FunctionTypeBuilder( fnName, compiler, node, sourceName, scope) .inferTemplateTypeName(info

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>) .inferReturnType(info) .inferParameterTypes(info) .inferInheritance(info); // Infer the context type. boolean searchedForThisType = false; if (objNode != null) { if (objNode.getType() == Token.GETPROP && objNode.getLastChild().getString().equals("prototype")) { builder.inferThisType(info, objNode.getFirstChild()); searchedForThisType = true; } else if (objNode.getType() == Token.THIS) { builder.inferThisType(info, objNode.getJSType()); searchedForThisType = true; } } if (!searchedForThisType) { builder.inferThisType(info, (Node) null); } jsType = builder.buildAndRegister(); } } return jsType; } /** * Defines variable(s) or modifies types based on the content of the node * {@code n}. A variable definition creates variables in the current scope, * a function definition creates a binding, and an assignment updates the * type of the namespaces on which the definition is made (e.g. * {@code goog.FOO = 6}). * @param n a {@link Token#VAR}, {@link Token#FUNCTION} or * {@link Token#ASSIGN} node * @param parent {@code n}'s parent */ void define(Node n, Node parent) { Preconditions.checkState(sourceName != null); JSDocInfo info = n.getJSDocInfo(); switch (n.getType()) { case Token.CATCH: Node catchName = n.getFirstChild(); defineSlot(catchName, n, null); break; case Token.VAR: if (n.getChildCount() > 1) { if (info != null) { // multiple children compiler.report(JSError.make(sourceName, n, MULTIPLE_VAR_DEF)); } for (Node name : n.children()) { defineName(name, n, parent, name.getJSDocInfo()); } } else { Node name = n.getFirstChild(); defineName(name, n, parent, (info != null) ? info : name.getJSD

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>ocInfo()); } break; case Token.FUNCTION: int parentType = parent.getType(); Preconditions.checkState( (scope.isLocal() || parentType != Token.ASSIGN) && parentType != Token.NAME, "function defined as standalone function when it is being " + "assigned"); String functionName = n.getFirstChild().getString(); FunctionType functionType = getFunctionType(functionName, n, info, null); if (NodeUtil.isFunctionDeclaration(n)) { defineSlot(n.getFirstChild(), n, functionType); } break; case Token.ASSIGN: // TODO(nicksantos): We should support direct assignment to a // prototype, as in: // Foo.prototype = { // a: function() { ... }, // b: function() { ... } // }; // Right now (6/23/08), we understand most of this syntax, but we // don't tie the "a" and "b" methods to the context of Foo. Node rvalue = n.getLastChild(); Node lvalue = n.getFirstChild(); info = (info != null) ? info : rvalue.getJSDocInfo(); if (rvalue.getType() == Token.FUNCTION || info != null && info.isConstructor()) { getFunctionType(lvalue.getQualifiedName(), rvalue, info, lvalue); } else if (info != null && info.hasEnumParameterType()) { lvalue.setJSType( getEnumType(lvalue.getQualifiedName(), n, rvalue, info.getEnumParameterType().evaluate(scope))); } break; default: throw new IllegalStateException(Integer.toString(n.getType())); } } /** * Defines a variable based on the {@link Token#NAME} node passed. * @param name The {@link Token#NAME} node. * @param var The parent of the {@code name} node, which must be a * {@link Token#VAR} node. * @param parent {@code var}'s parent. * @param info the {@link JSDocInfo} information relating to this * {@code name} node. */ private void defineName(Node name, Node var, Node

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> parent, JSDocInfo info) { Node value = name.getFirstChild(); if (value != null && value.getType() == Token.FUNCTION) { // function String functionName = name.getString(); FunctionType functionType = getFunctionType(functionName, value, info, null); defineSlot(name, var, functionType); } else { // variable's type JSType type = null; if (info == null) { // the variable's type will be inferred CompilerInput input = compiler.getInput(sourceName); Preconditions.checkNotNull(input, sourceName); type = input.isExtern() ? typeRegistry.getNativeType(UNKNOWN_TYPE) : null; } else if (info.hasEnumParameterType()) { type = getEnumType(name.getString(), var, value, info.getEnumParameterType().evaluate(scope)); } else if (info.isConstructor()) { type = getFunctionType(name.getString(), value, info, name); } else { type = getDeclaredTypeInAnnotation(sourceName, name, info); } defineSlot(name, var, type); } } /** * Gets the function type from the function node and its attached * {@link JSDocInfo}. * @param name the function's name * @param rValue the function node. It must be a {@link Token#FUNCTION}. * @param info the {@link JSDocInfo} attached to the function definition * @param lvalueNode The node where this function is being * assigned. For example, {@code A.prototype.foo = ...} would be used to * determine that this function is a method of A.prototype. May be * null to indicate that this is not being assigned to a qualified name. */ private FunctionType getFunctionType(String name, Node rValue, JSDocInfo info, @Nullable Node lvalueNode) { FunctionType functionType = null; // Handle function aliases. if (rValue != null && rValue.isQualifiedName()) { Var var = scope.getVar(rValue.getQualifiedName()); if (var != null && var.getType() instanceof FunctionType) { functionType = (FunctionType) var.getType(); if (functionType != null && function

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>Type.isConstructor()) { typeRegistry.declareType(name, functionType.getInstanceType()); } } return functionType; } Node owner = null; if (lvalueNode != null) { owner = getPrototypePropertyOwner(lvalueNode); } Node errorRoot = rValue == null ? lvalueNode : rValue; boolean isFnLiteral = rValue != null && rValue.getType() == Token.FUNCTION; Node fnRoot = isFnLiteral ? rValue : null; Node parametersNode = isFnLiteral ? rValue.getFirstChild().getNext() : null; if (functionType == null && info != null && info.hasType()) { JSType type = info.getType().evaluate(scope); // Known to be not null since we have the FUNCTION token there. type = type.restrictByNotNullOrUndefined(); if (type.isFunctionType()) { functionType = (FunctionType) type; functionType.setJSDocInfo(info); } } if (functionType == null) { if (info == null || !FunctionTypeBuilder.isFunctionTypeDeclaration(info)) { // We don't really have any type information in the annotation. // Before we give up on this function, look at the object we're // assigning it to. For example, if the function looks like this: // SubFoo.prototype.bar = function() { ... }; // We can use type information on Foo.prototype.bar and apply it // to this function. if (lvalueNode != null && lvalueNode.getType() == Token.GETPROP && lvalueNode.isQualifiedName()) { Var var = scope.getVar( lvalueNode.getFirstChild().getQualifiedName()); if (var != null) { ObjectType ownerType = ObjectType.cast(var.getType()); FunctionType propType = null; if (ownerType != null) { propType = findOverriddenFunction( ownerType, lvalueNode.getLastChild().getString()); } if (propType != null) { functionType = new FunctionTypeBuilder( name, compiler, errorRoot, sourceName, scope) .setSourceNode(fnRoot) .inferFromOverriddenFunction(propType, parametersNode) .inferThisType(

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>TypeNative#UNKNOWN_TYPE} if its type is * inferred. * * Slots may be any variable or any qualified name in the global scope. * * @param n the defining NAME or GETPROP node. * @param parent the {@code n}'s parent. * @param type the variable's type. It may be {@code null} if * {@code inferred} is {@code true}. */ void defineSlot(Node n, Node parent, JSType type, boolean inferred) { Preconditions.checkArgument(inferred || type != null); // Only allow declarations of NAMEs and qualfied names. boolean shouldDeclareOnGlobalThis = false; if (n.getType() == Token.NAME) { Preconditions.checkArgument( parent.getType() == Token.FUNCTION || parent.getType() == Token.VAR || parent.getType() == Token.LP || parent.getType() == Token.CATCH); shouldDeclareOnGlobalThis = scope.isGlobal() && (parent.getType() == Token.VAR || parent.getType() == Token.FUNCTION); } else { Preconditions.checkArgument( n.getType() == Token.GETPROP && (parent.getType() == Token.ASSIGN || parent.getType() == Token.EXPR_RESULT)); } String variableName = n.getQualifiedName(); Preconditions.checkArgument(!variableName.isEmpty()); // declared in closest scope? if (scope.isDeclared(variableName, false)) { Var oldVar = scope.getVar(variableName); validator.expectUndeclaredVariable( sourceName, n, parent, oldVar, variableName, type); } else { if (!inferred) { n.setJSType(type); } CompilerInput input = compiler.getInput(sourceName); scope.declare(variableName, n, type, input, inferred); if (shouldDeclareOnGlobalThis) { ObjectType globalThis = typeRegistry.getNativeObjectType(JSTypeNative.GLOBAL_THIS); boolean isExtern = input.isExtern(); if (inferred) { globalThis.defineInferredProperty(variableName, type == null ? typeRegistry.getNativeType(JSTypeNative.NO_TYPE) : type, isExtern);

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>getReferenceName(), null, null, null); delegateProxy.setPrototypeBasedOn(delegateBaseCtor); codingConvention.applyDelegateRelationship( delegateSuperObject, delegateBaseObject, delegatorObject, delegateProxy, findDelegate); delegateProxyMap.put( delegateProxy.getPrototype(), delegateBaseCtor.getPrototype()); } } } ObjectLiteralCast objectLiteralCast = codingConvention.getObjectLiteralCast(t, n); if (objectLiteralCast != null) { ObjectType type = ObjectType.cast( typeRegistry.getType(objectLiteralCast.typeName)); if (type != null && type.getConstructor() != null) { objectLiteralCast.objectNode.setJSType(type); } else { compiler.report(JSError.make(t.getSourceName(), n, CONSTRUCTOR_EXPECTED)); } } break; case Token.FUNCTION: if (!t.getInput().isExtern()) { nonExternFunctions.add(n); } // VARs and ASSIGNs are handled separately. if (parent.getType() == Token.ASSIGN || parent.getType() == Token.NAME) { return; } define(n, parent); break; case Token.ASSIGN: // Handle constructor and enum definitions. define(n, parent); // Handle typedefs. checkForOldStyleTypedef(t, n); // Handle initialization of properties. Node firstChild = n.getFirstChild(); if (firstChild.getType() == Token.GETPROP && firstChild.isQualifiedName()) { maybeDeclareQualifiedName(t, n.getJSDocInfo(), firstChild, n, firstChild.getNext()); } break; case Token.CATCH: define(n, parent); break; case Token.VAR: define(n, parent); // Handle typedefs. if (n.hasOneChild()) { checkForOldStyleTypedef(t, n); checkForTypedef(t, n.getFirstChild(), n.getJSDocInfo()); } break; case Token.GETPROP: // Handle stubbed properties. if (parent.getType() == Token.EXPR_RESULT && n.isQualifiedName()) { checkForTypedef(t, n, n.get

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> JSError.make( t.getSourceName(), candidate, MALFORMED_TYPEDEF, typedef)); } typeRegistry.declareType(typedef, realType); // Duplicate typedefs get handled when we try to register // this typedef in the scope. } } /** * Declare the symbol for a qualified name in the global scope. * * @param info The doc info for this property. * @param n A top-level GETPROP node (it should not be contained inside * another GETPROP). * @param parent The parent of {@code n}. * @param rhsValue The node that {@code n} is being initialized to, * or {@code null} if this is a stub declaration. */ private void maybeDeclareQualifiedName(NodeTraversal t, JSDocInfo info, Node n, Node parent, Node rhsValue) { Node ownerNode = n.getFirstChild(); String ownerName = ownerNode.getQualifiedName(); String qName = n.getQualifiedName(); String propName = n.getLastChild().getString(); Preconditions.checkArgument(qName != null && ownerName != null); // Function prototypes are special. // It's a common JS idiom to do: // F.prototype = { ... }; // So if F does not have an explicitly declared super type, // allow F.prototype to be redefined arbitrarily. if ("prototype".equals(propName)) { Var qVar = scope.getVar(qName); if (qVar != null) { if (!qVar.isTypeInferred()) { // Just ignore assigns to declared prototypes. return; } scope.undeclare(qVar); } } // Precedence of type information on GETPROPs: // 1) @type annotation / @enum annotation // 2) ASSIGN to FUNCTION literal // 3) @param/@return annotation (with no function literal) // 4) ASSIGN to anything else // // 1 and 3 are declarations, 4 is inferred, and 2 is a declaration iff // the function has not been declared before. // // FUNCTION literals are special because TypedScopeCreator is very smart // about getting as much type information as possible for them. // Determining type for

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> #1 + #2 + #3 JSType valueType = getDeclaredGetPropType(t, info, n, rhsValue); if (valueType == null && rhsValue != null) { // Determining type for #4 valueType = rhsValue.getJSType(); } if (valueType == null) { if (parent.getType() == Token.EXPR_RESULT) { stubDeclarations.add(new StubDeclaration( n, t.getInput().isExtern(), ownerName)); } else if (rhsValue != null && rhsValue.getType() == Token.TRUE) { // We declare these for delegate proxy method properties. ObjectType ownerType = getObjectSlot(ownerName); if (ownerType instanceof FunctionType) { JSType ownerTypeOfThis = ((FunctionType) ownerType).getTypeOfThis(); String delegateName = codingConvention.getDelegateSuperclassName(); JSType delegateType = delegateName == null ? null : typeRegistry.getType(delegateName); if (delegateType != null && ownerTypeOfThis.isSubtype(delegateType)) { defineSlot(n, parent, typeRegistry.getNativeType(BOOLEAN_TYPE), true); } } } return; } boolean inferred = true; if (info != null) { // Determining declaration for #1 + #3 inferred = !(info.hasType() || info.hasEnumParameterType() || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred) { // Determining declaration for #2 inferred = !(rhsValue != null && rhsValue.getType() == Token.FUNCTION && !scope.isDeclared(qName, false)); } if (!inferred) { ObjectType ownerType = getObjectSlot(ownerName); if (ownerType != null) { // Only declare this as an official property if it has not been // declared yet. boolean isExtern = t.getInput().isExtern(); if ((!ownerType.hasOwnProperty(propName) || ownerType.isPropertyTypeInferred(propName)) && ((isExtern && !ownerType.isNativeObjectType()) || !ownerType.isInstanceType())) { // If the property is undeclared or inferred, declare it now. ownerType.defineDeclaredProperty(propName,

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> valueType, isExtern); } } // If the property is already declared, the error will be // caught when we try to declare it in the current scope. defineSlot(n, parent, valueType, inferred); } } /** * Find the ObjectType associated with the given slot. * @param slotName The name of the slot to find the type in. * @return An object type, or null if this slot does not contain an object. */ private ObjectType getObjectSlot(String slotName) { Var ownerVar = scope.getVar(slotName); if (ownerVar != null) { JSType ownerVarType = ownerVar.getType(); return ObjectType.cast(ownerVarType == null ? null : ownerVarType.restrictByNotNullOrUndefined()); } return null; } /** * Look for a type declaration on a GETPROP node. * * @param info The doc info for this property. * @param n A top-level GETPROP node (it should not be contained inside * another GETPROP). * @param rhsValue The node that {@code n} is being initialized to, * or {@code null} if this is a stub declaration. */ private JSType getDeclaredGetPropType(NodeTraversal t, JSDocInfo info, Node n, Node rhsValue) { if (info != null && info.hasType()) { return getDeclaredTypeInAnnotation(t, n, info); } else if (info != null && info.hasEnumParameterType()) { return n.getJSType(); } else if (rhsValue != null && rhsValue.getType() == Token.FUNCTION) { return rhsValue.getJSType(); } else { return getDeclaredTypeInAnnotation(t, n, info); } } /** * Resolve any stub delcarations to unknown types if we could not * find types for them during traversal. */ private void resolveStubDeclarations() { for (StubDeclaration stub : stubDeclarations) { Node n = stub.node; Node parent = n.getParent(); String qName = n.getQualifiedName(); String propName = n.getLastChild().getString(); String ownerName = stub.ownerName; boolean isExtern = stub.

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> // if the member expression is not of the form: this.someProperty. if (info == null || member.getType() != Token.GETPROP || member.getFirstChild().getType() != Token.THIS) { return; } member.getFirstChild().setJSType(thisType); JSType jsType = getDeclaredTypeInAnnotation(t, member, info); Node name = member.getLastChild(); if (jsType != null && (name.getType() == Token.NAME || name.getType() == Token.STRING)) { thisType.defineDeclaredProperty( name.getString(), jsType, false /* functions with implementations are not in externs */); } } } // end CollectProperties } // end GlobalScopeBuilder /** * A shallow traversal of a local scope to find all arguments and * local variables. */ private final class LocalScopeBuilder extends AbstractScopeBuilder { /** * @param scope The scope that we're builidng. */ private LocalScopeBuilder(Scope scope) { super(scope); } /** * Traverse the scope root and build it. */ void build() { NodeTraversal.traverse(compiler, scope.getRootNode(), this); } /** * Visit a node in a local scope, and add any local variables or catch * parameters into the local symbol table. * * @param t The node traversal. * @param n The node being visited. * @param parent The parent of n */ @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n == scope.getRootNode()) return; if (n.getType() == Token.LP && parent == scope.getRootNode()) { handleFunctionInputs(parent); return; } switch (n.getType()) { case Token.FUNCTION: // VARs and ASSIGNs are handled separately. if (parent.getType() == Token.NAME) { return; } define(n, parent); break; case Token.CATCH: case Token.VAR: define(n, parent); break; } } /** Handle bleeding functions and function parameters. */ private void handleFunctionInputs(Node fnNode) { // Handle bleed

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> null} to indicate * that the return type is unknown. * @param typeOfThis The type of {@code this} in non-constructors. May be * {@code null} to indicate that the type of {@code this} is unknown. * @param templateTypeName The template type name or {@code null}. */ public FunctionType(JSTypeRegistry registry, String name, Node source, Node parameters, JSType returnType, ObjectType typeOfThis, String templateTypeName) { this(registry, name, source, parameters, returnType, typeOfThis, templateTypeName, false, false); } /** Creates an instance for a function that might be a constructor. */ FunctionType(JSTypeRegistry registry, String name, Node source, Node parameters, JSType returnType, ObjectType typeOfThis, String templateTypeName, boolean isConstructor, boolean nativeType) { super(registry, name, registry.getNativeObjectType(JSTypeNative.FUNCTION_INSTANCE_TYPE), nativeType); Preconditions.checkArgument(source == null || Token.FUNCTION == source.getType()); this.source = source; this.kind = isConstructor ? Kind.CONSTRUCTOR : Kind.ORDINARY; if (isConstructor) { this.typeOfThis = typeOfThis != null && typeOfThis.isNoObjectType() ? typeOfThis : new InstanceObjectType(registry, this, nativeType); } else { this.typeOfThis = typeOfThis != null ? typeOfThis : registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE); } // The call type should be set up last because we are calling getReturnType, // which may be overloaded and depend on other properties being set. this.call = new ArrowType(registry, parameters, (returnType == null ? getReturnType() : returnType)); this.templateTypeName = templateTypeName; } /** Creates an instance for a function that is an interface. */ FunctionType(JSTypeRegistry registry, String name, Node source) { super(registry, name, registry.getNativeObjectType(JSTypeNative.FUNCTION_INSTANCE_TYPE)); Preconditions.checkArgument(source == null || Token.FUNCTION == source.getType()); Preconditions.checkArgument(name != null); this.source = source; this.call = null; this

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>); if (functionInstance.equals(that)) { return that; } else if (functionInstance.equals(this)) { return this; } return registry.getNativeType(JSTypeNative.U2U_CONSTRUCTOR_TYPE); } return super.getLeastSupertype(that); } @Override public JSType getGreatestSubtype(JSType that) { if (isFunctionType() && that.isFunctionType()) { if (equals(that)) { return this; } JSType functionInstance = registry.getNativeType( JSTypeNative.FUNCTION_INSTANCE_TYPE); if (functionInstance.equals(that)) { return this; } else if (functionInstance.equals(this)) { return that; } return registry.getNativeType(JSTypeNative.NO_OBJECT_TYPE); } return super.getGreatestSubtype(that); } /** * Given a constructor or an interface type, get its superclass constructor * or {@code null} if none exists. */ public FunctionType getSuperClassConstructor() { Preconditions.checkArgument(isConstructor() || isInterface()); ObjectType maybeSuperInstanceType = getPrototype().getImplicitPrototype(); if (maybeSuperInstanceType == null) { return null; } return maybeSuperInstanceType.getConstructor(); } /** * Given a constructor or an interface type, find out whether the unknown * type is a supertype of the current type. */ public boolean hasUnknownSupertype() { Preconditions.checkArgument(isConstructor() || isInterface()); Preconditions.checkArgument(!this.isUnknownType()); // Potential infinite loop if our type system messes up or someone defines // a bad type. Otherwise the loop should always end. FunctionType ctor = this; while (true) { ObjectType maybeSuperInstanceType = ctor.getPrototype().getImplicitPrototype(); if (maybeSuperInstanceType == null) { return false; } if (maybeSuperInstanceType.isUnknownType()) { return true; } ctor = maybeSuperInstanceType.getConstructor(); if (ctor == null) { return false; } Preconditions.checkState(ctor.isConstructor() || ctor.isInterface()); } } /**

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS> * Given a constructor or an interface type and a property, finds the * top-most superclass that has the property defined (including this * constructor). */ public JSType getTopMostDefiningType(String propertyName) { Preconditions.checkState(isConstructor() || isInterface()); Preconditions.checkArgument(getPrototype().hasProperty(propertyName)); FunctionType ctor = this; JSType topInstanceType; do { topInstanceType = ctor.getInstanceType(); ctor = ctor.getSuperClassConstructor(); } while (ctor != null && ctor.getPrototype().hasProperty(propertyName)); return topInstanceType; } /** * Two function types are equal if their signatures match. Since they don't * have signatures, two interfaces are equal if their names match. */ @Override public boolean equals(Object otherType) { if (!(otherType instanceof FunctionType)) { return false; } FunctionType that = (FunctionType) otherType; if (!that.isFunctionType()) { return false; } if (this.isConstructor()) { if (that.isConstructor()) { return this == that; } return false; } if (this.isInterface()) { if (that.isInterface()) { return this.getReferenceName().equals(that.getReferenceName()); } return false; } if (that.isInterface()) { return false; } return this.typeOfThis.equals(that.typeOfThis) && this.call.equals(that.call); } @Override public int hashCode() { return isInterface() ? getReferenceName().hashCode() : call.hashCode(); } public boolean hasEqualCallType(FunctionType otherType) { return this.call.equals(otherType.call); } /** * Informally, a function is represented by * {@code function (params): returnType} where the {@code params} is a comma * separated list of types, the first one being a special * {@code this:T} if the function expects a known type for {@code this}. */ @Override public String toString() { if (this == registry.getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE)) { return "Function"; } StringBuilder b =

Closure, 141

<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB> * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !NodeUtil.isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; <CHANGES> <CHANGEE> // Anonymous functions definitions are not changed by side-effects, // and named functions are not part of expressions. <CHANGES> <CHANGEE> } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: <FILEE> <FILEB> functionInfo.toString() + " Calls: " + depFunctionNames + "\n"); } return sb.toString(); } /** * Query the DefinitionProvider for the list of definitions that * correspond to a given qualified name subtree. Return null if * DefinitionProvider does not contain an entry for a given name, * one or more of the values returned by getDeclarations is not * callable, or the "name" node is not a GETPROP or NAME. * * @param definitionProvider The name reference graph * @param name Query node * @return non-empty definition list or null */ private static Collection<Definition> getCallableDefinitions( DefinitionProvider definitionProvider, Node name) { <CHANGES> <CHANGEE> List<Definition> result = Lists.newArrayList(); <CHANGES> if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) { return null; } <CHANGEE> Collection<Definition> decls = definitionProvider.getDefinitionsReferencedAt(name); <SCANS>.isInterface()) { // An interface function cannot be assigned to anything. return false; } // If functionA is a subtype of functionB, then their "this" types // should be contravariant. However, this causes problems because // of the way we enforce overrides. Because function(this:SubFoo) // is not a subtype of function(this:Foo), our override check treats // this as an error. It also screws up out standard method // for aliasing constructors. Let's punt on all this for now. // TODO(nicksantos): fix this. FunctionType other = (FunctionType) that; return (this.isConstructor() || other.isConstructor() || other.typeOfThis.isSubtype(this.typeOfThis) || this.typeOfThis.isSubtype(other.typeOfThis)) && this.call.isSubtype(other.call); } if (that instanceof UnionType) { UnionType union = (UnionType) that; for (JSType element : union.alternates) { if (this.isSubtype(element)) { return true; } } } return getNativeType(JSTypeNative.FUNCTION_PROTOTYPE).isSubtype(that); } @Override public <T> T visit(Visitor<T> visitor) { return visitor.caseFunctionType(this); } /** * Gets the type of instance of this function. * @throws IllegalStateException if this function is not a constructor * (see {@link #isConstructor()}). */ public ObjectType getInstanceType() { Preconditions.checkState(hasInstanceType()); return typeOfThis; } /** Sets the instance type. This should only be used for special native types. */ void setInstanceType(ObjectType instanceType) { typeOfThis = instanceType; } /** * Returns whether this function type has an instance type. */ public boolean hasInstanceType() { return isConstructor() || isInterface(); } /** * Gets the type of {@code this} in this function. */ public ObjectType getTypeOfThis() { return typeOfThis.isNoObjectType() ? registry.getNativeObjectType(JSTypeNative.OBJECT_TYPE) : typeOfThis; } /**